diff --git a/clashoo/Makefile b/clashoo/Makefile
index b9b4c60ad..77c66b06d 100644
--- a/clashoo/Makefile
+++ b/clashoo/Makefile
@@ -5,13 +5,13 @@ PKG_SHORT_SHA:=bc76c13
PKG_COMMIT_DATE:=2026.10.11
PKG_VERSION:=$(PKG_COMMIT_DATE)~$(PKG_SHORT_SHA)
-PKG_RELEASE:=10
+PKG_RELEASE:=11
PKG_SOURCE:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA).tar.gz
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA)
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-alpha-$(PKG_SHORT_SHA)
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src
-PKG_SOURCE_VERSION:=29c93e5739479dc14c0dea398465f3bc17026944
+PKG_SOURCE_VERSION:=fac50d4bfa20e397dc765616578d92522b208e3a
PKG_HASH:=skip
PKG_BUILD_VERSION:=alpha-$(PKG_SHORT_SHA)
diff --git a/ksmbd-tools/Makefile b/ksmbd-tools/Makefile
new file mode 100644
index 000000000..777a7ab43
--- /dev/null
+++ b/ksmbd-tools/Makefile
@@ -0,0 +1,119 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=ksmbd-tools
+PKG_VERSION:=3.5.7
+PKG_RELEASE:=1
+
+PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
+PKG_SOURCE_URL:=https://github.com/cifsd-team/ksmbd-tools/releases/download/$(PKG_VERSION)
+PKG_HASH:=skip
+
+PKG_LICENSE:=GPL-2.0-or-later
+PKG_LICENSE_FILES:=COPYING
+
+PKG_BUILD_DEPENDS:=glib2
+PKG_BUILD_FLAGS:=gc-sections
+
+include $(INCLUDE_DIR)/package.mk
+include $(INCLUDE_DIR)/nls.mk
+include $(INCLUDE_DIR)/meson.mk
+
+define Package/ksmbd-tools/Default
+ SECTION:=net
+ CATEGORY:=Network
+ SUBMENU:=Filesystem
+ TITLE:=Kernel SMB
+ URL:=https://github.com/cifsd-team/ksmbd-tools
+ DEPENDS:= $(ICONV_DEPENDS) $(INTL_DEPENDS)
+endef
+
+define Package/ksmbd-tools/Default/description
+ Userspace tools to manage the SMB kernel fileserver (ksmbd.ko).
+ The config file location is /etc/ksmbd/ksmbd.conf
+endef
+
+define Package/ksmbd-server
+ $(call Package/ksmbd-tools/Default)
+ TITLE+= server
+ DEPENDS+= +(LINUX_5_4||LINUX_5_10||LINUX_6_6):kmod-fs-cifsd +!(LINUX_5_4||LINUX_5_10||LINUX_6_6):kmod-fs-ksmbd +libnl-core +libnl-genl +umdns +flock
+endef
+
+define Package/ksmbd-server/description
+ installs: ksmbd.mountd
+
+ This provides the basic fileserver service and is the minimum needed to serve 'guest only' file shares or use a existing user database file ksmbdpwd.db.
+endef
+
+define Package/ksmbd-server/config
+ select PACKAGE_wsdd2
+endef
+
+define Package/ksmbd-avahi-service
+ $(call Package/ksmbd-tools/Default)
+ TITLE+= (Avahi service)
+ DEPENDS:= +avahi-daemon
+endef
+
+define Package/ksmbd-avahi-service/description
+ installs: smb.service
+
+ This package contains the service definition for announcing the
+ Ksmbd (smb/445) Daemon service via mDNS/DNS-SD.
+endef
+
+define Package/ksmbd-hotplug
+ $(call Package/ksmbd-tools/Default)
+ TITLE+= hotplug script for auto sharing
+ DEPENDS:=+blockd
+endef
+
+MESON_ARGS += \
+ -Db_lto=true \
+ -Dkrb5=disabled
+
+TARGET_LDFLAGS += -Wl,--as-needed
+
+define Package/ksmbd-server/install
+ $(INSTALL_DIR) $(1)/usr/libexec
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/libexec/ksmbd.tools $(1)/usr/libexec/
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(CP) $(PKG_INSTALL_DIR)/usr/sbin/ksmbd.addshare $(1)/usr/sbin/
+ $(CP) $(PKG_INSTALL_DIR)/usr/sbin/ksmbd.adduser $(1)/usr/sbin/
+ $(CP) $(PKG_INSTALL_DIR)/usr/sbin/ksmbd.control $(1)/usr/sbin/
+ $(CP) $(PKG_INSTALL_DIR)/usr/sbin/ksmbd.mountd $(1)/usr/sbin/
+ $(INSTALL_DIR) $(1)/etc/config $(1)/etc/ksmbd $(1)/etc/init.d
+ $(INSTALL_CONF) ./files/ksmbd.config $(1)/etc/config/ksmbd
+ $(INSTALL_DATA) ./files/ksmbd.conf.template $(1)/etc/ksmbd/
+ $(INSTALL_BIN) ./files/ksmbd.init $(1)/etc/init.d/ksmbd
+ $(INSTALL_DIR) $(1)/etc/sysctl.d
+ $(INSTALL_CONF) ./files/ksmbd.sysctl $(1)/etc/sysctl.d/90-ksmbd.conf
+ $(INSTALL_DATA) ./files/shares.sh $(1)/etc/ksmbd/
+ # copy examples until we have a wiki page
+ $(INSTALL_DATA) ./files/ksmbd.config.example $(1)/etc/ksmbd/
+endef
+
+define Package/ksmbd-avahi-service/install
+ $(INSTALL_DIR) $(1)/etc/avahi/services
+ $(INSTALL_DATA) ./files/smb.service $(1)/etc/avahi/services/
+endef
+
+define Package/ksmbd-hotplug/install
+ $(INSTALL_DIR) $(1)/etc/hotplug.d/mount
+ $(INSTALL_CONF) ./files/ksmbd.hotplug $(1)/etc/hotplug.d/mount/60-ksmbd
+endef
+
+define Package/ksmbd-server/conffiles
+/etc/config/ksmbd
+/etc/ksmbd/ksmbd.conf.template
+/etc/ksmbd/ksmbd.conf
+/etc/ksmbd/ksmbdpwd.db
+/etc/sysctl.d/90-ksmbd.conf
+endef
+
+define Package/ksmbd-avahi-service/conffiles
+/etc/avahi/services/smb.service
+endef
+
+$(eval $(call BuildPackage,ksmbd-server))
+$(eval $(call BuildPackage,ksmbd-avahi-service))
+$(eval $(call BuildPackage,ksmbd-hotplug))
diff --git a/ksmbd-tools/files/ksmbd.conf.template b/ksmbd-tools/files/ksmbd.conf.template
new file mode 100644
index 000000000..9daf4258a
--- /dev/null
+++ b/ksmbd-tools/files/ksmbd.conf.template
@@ -0,0 +1,15 @@
+[global]
+ netbios name = |NAME|
+ server string = |DESCRIPTION|
+ workgroup = |WORKGROUP|
+ interfaces = |INTERFACES|
+ bind interfaces only = yes
+ ipc timeout = 20
+ deadtime = 15
+ map to guest = Bad User
+ server max protocol = SMB3_11
+ smb2 max read = 1MB
+ smb2 max write = 1MB
+ smb2 max trans = 1MB
+ cache read buffers = no
+ cache trans buffers = no
diff --git a/ksmbd-tools/files/ksmbd.config b/ksmbd-tools/files/ksmbd.config
new file mode 100644
index 000000000..33d8bc771
--- /dev/null
+++ b/ksmbd-tools/files/ksmbd.config
@@ -0,0 +1,9 @@
+config globals
+ option enabled '0'
+ option 'workgroup' 'WORKGROUP'
+ option 'description' 'Ksmbd on OpenWrt'
+ option interface 'lan'
+ option allow_legacy_protocols '1'
+ option allow_guest_ipc '1'
+ option homes '0'
+ option autoshare '1'
diff --git a/ksmbd-tools/files/ksmbd.config.example b/ksmbd-tools/files/ksmbd.config.example
new file mode 100644
index 000000000..9a457e03b
--- /dev/null
+++ b/ksmbd-tools/files/ksmbd.config.example
@@ -0,0 +1,11 @@
+config globals
+ option 'description' 'Ksmbd on OpenWrt'
+
+config share
+ option name 'testshare'
+ option path '/tmp'
+ option read_only 'no'
+ option guest_ok 'yes'
+ option create_mask '0666'
+ option dir_mask '0777'
+ option force_root '1'
diff --git a/ksmbd-tools/files/ksmbd.hotplug b/ksmbd-tools/files/ksmbd.hotplug
new file mode 100644
index 000000000..56b68b300
--- /dev/null
+++ b/ksmbd-tools/files/ksmbd.hotplug
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+. /lib/functions.sh
+case "$ACTION" in add|remove) ;; *) exit 0;; esac
+/etc/init.d/ksmbd enabled || exit 0
+
+enabled=0
+autoshare=1
+ksmbd_settings() {
+ config_get_bool enabled "$1" enabled 0
+ config_get_bool autoshare "$1" autoshare 1
+}
+config_load ksmbd
+config_foreach ksmbd_settings globals
+[ "$enabled" -eq 1 ] && [ "$autoshare" -eq 1 ] || exit 0
+
+# Synchronize automatic UCI shares and rebuild from current mounts.
+/etc/init.d/ksmbd reload
diff --git a/ksmbd-tools/files/ksmbd.init b/ksmbd-tools/files/ksmbd.init
new file mode 100644
index 000000000..14f049786
--- /dev/null
+++ b/ksmbd-tools/files/ksmbd.init
@@ -0,0 +1,211 @@
+#!/bin/sh /etc/rc.common
+
+START=98
+STOP=05
+USE_PROCD=1
+
+SMBD_IFACE=""
+KSMBD_ENABLED=0
+KSMBD_HOMES=0
+KSMBD_AUTOSHARE=1
+
+config_get_sane()
+{
+ config_get "$@"
+ set -- "$(echo "$1" | tr -d '<>[]{};%?=#\n')"
+}
+
+smb_header()
+{
+ config_get_bool KSMBD_ENABLED "$1" enabled 0
+ config_get_bool KSMBD_HOMES "$1" homes 0
+ config_get_bool KSMBD_AUTOSHARE "$1" autoshare 1
+ config_get_sane SMBD_IFACE "$1" interface "lan"
+
+ # resolve interfaces
+ interfaces=$(
+ . /lib/functions/network.sh
+
+ for net in $SMBD_IFACE; do
+ network_is_up "$net" || continue
+ network_get_device device "$net"
+ printf "%s " "${device:-$net}"
+ done
+ )
+
+ config_get hostname "$1" name
+ case "$hostname" in
+ ''|*[!a-zA-Z0-9_.-]*)
+ hostname="$(sed 's/\..*//' /proc/sys/kernel/hostname | tr -cd 'a-zA-Z0-9_-')"
+ ;;
+ esac
+
+ config_get_sane workgroup "$1" workgroup "WORKGROUP"
+ config_get_sane description "$1" description "Ksmbd on OpenWrt"
+ config_get_bool ALLOW_LEGACY_PROTOCOLS "$1" allow_legacy_protocols 1
+ config_get_bool ALLOW_GUEST_IPC "$1" allow_guest_ipc 1
+
+ sed -e "s#|NAME|#$hostname#g" \
+ -e "s#|WORKGROUP|#$workgroup#g" \
+ -e "s#|DESCRIPTION|#$description#g" \
+ -e "s#|INTERFACES|#$interfaces#g" \
+ /etc/ksmbd/ksmbd.conf.template > /var/etc/ksmbd/ksmbd.conf
+
+ {
+ printf "\n######### Dynamic written config options #########\n"
+
+ if [ "$ALLOW_LEGACY_PROTOCOLS" -eq 1 ]; then
+ logger -p daemon.info -t 'ksmbd' "Legacy Protocols allowed, don't use this option for secure environments!"
+ printf "\tserver min protocol = NT1\n"
+ printf "\tserver signing = disabled\n"
+ fi
+ printf "\n[IPC$]\n\tguest ok = %s\n" "$( [ "$ALLOW_GUEST_IPC" -eq 1 ] && echo yes || echo no )"
+ } >> /var/etc/ksmbd/ksmbd.conf
+
+ [ -e /etc/ksmbd/ksmbd.conf ] || ln -nsf /var/etc/ksmbd/ksmbd.conf /etc/ksmbd/ksmbd.conf
+
+ if [ ! -L /etc/ksmbd/ksmbd.conf ]; then
+ logger -p daemon.warn -t 'ksmbd' "Local custom /etc/ksmbd/ksmbd.conf file detected, all UCI/Luci config settings are ignored!"
+ fi
+}
+
+smb_add_share()
+{
+ config_get_sane name "$1" name
+ config_get_sane path "$1" path
+ config_get_sane comment "$1" comment
+ config_get_sane users "$1" users
+ config_get_sane create_mask "$1" create_mask 0666
+ config_get_sane dir_mask "$1" dir_mask 0777
+ config_get_sane browseable "$1" browseable yes
+ config_get_sane read_only "$1" read_only no
+ config_get_sane writeable "$1" writeable
+ config_get_sane guest_ok "$1" guest_ok yes
+ config_get_bool force_root "$1" force_root 1
+ config_get_sane write_list "$1" write_list
+ config_get_sane read_list "$1" read_list
+ config_get_sane hide_dot_files "$1" hide_dot_files yes
+ config_get_sane veto_files "$1" veto_files
+ config_get_sane inherit_owner "$1" inherit_owner
+ config_get_sane force_create_mode "$1" force_create_mode
+ config_get_sane force_directory_mode "$1" force_directory_mode
+
+ [ -z "$name" ] || [ -z "$path" ] && return
+
+ {
+ printf "\n[%s]\n\tpath = %s\n" "$name" "$path"
+ [ -n "$comment" ] && printf "\tcomment = %s\n" "$comment"
+
+ if [ "$force_root" -eq 1 ]; then
+ printf "\tforce user = %s\n" "root"
+ printf "\tforce group = %s\n" "root"
+ fi
+ [ -n "$users" ] && printf "\tvalid users = %s\n" "$users"
+
+ [ -n "$create_mask" ] && printf "\tcreate mask = %s\n" "$create_mask"
+ [ -n "$dir_mask" ] && printf "\tdirectory mask = %s\n" "$dir_mask"
+ [ -n "$force_create_mode" ] && printf "\tforce create mode = %s\n" "$force_create_mode"
+ [ -n "$force_directory_mode" ] && printf "\tforce directory mode = %s\n" "$force_directory_mode"
+
+ [ -n "$browseable" ] && printf "\tbrowseable = %s\n" "$browseable"
+ [ -n "$read_only" ] && printf "\tread only = %s\n" "$read_only"
+ [ -n "$writeable" ] && printf "\twriteable = %s\n" "$writeable"
+
+ [ -n "$guest_ok" ] && printf "\tguest ok = %s\n" "$guest_ok"
+ [ -n "$inherit_owner" ] && printf "\tinherit owner = %s\n" "$inherit_owner"
+
+ [ -n "$write_list" ] && printf "\twrite list = %s\n" "$write_list"
+ [ -n "$read_list" ] && printf "\tread list = %s\n" "$read_list"
+
+ [ -n "$hide_dot_files" ] && printf "\thide dot files = %s\n" "$hide_dot_files"
+ [ -n "$veto_files" ] && printf "\tveto files = %s\n" "$veto_files"
+ } >> /var/etc/ksmbd/ksmbd.conf
+}
+
+init_config()
+{
+ flock -x 9 || return 1
+ mkdir -p /var/etc/ksmbd
+
+ config_load ksmbd
+ config_foreach smb_header globals
+ . /etc/ksmbd/shares.sh
+ ksmbd_sync_mounts || return 1
+ config_load ksmbd
+ config_foreach smb_add_share share
+ [ "$KSMBD_HOMES" -eq 1 ] && ksmbd_add_homes
+ return 0
+} 9>/var/lock/ksmbd-config.lock
+
+service_triggers()
+{
+ # PROCD_RELOAD_DELAY=1000
+
+ procd_add_reload_trigger "dhcp" "system" "ksmbd"
+
+ for i in $SMBD_IFACE; do
+ procd_add_reload_interface_trigger "$i"
+ done
+}
+
+kill_server()
+{
+ if [ -e /sys/module/ksmbd ]; then
+ if [ -e /sys/class/ksmbd-control/kill_server ]; then
+ logger -p daemon.info -t 'ksmbd' "triggering kill_server"
+ echo hard > /sys/class/ksmbd-control/kill_server
+ fi
+ fi
+}
+
+start_service()
+{
+ init_config || return 1
+ if [ "$KSMBD_ENABLED" -ne 1 ]; then
+ stop_service
+ return 0
+ fi
+
+ if [ ! -e /etc/ksmbd/ksmbd.conf ]; then
+ logger -p daemon.error -t 'ksmbd' "missing config /etc/ksmbd/ksmbd.conf!"
+ exit 1
+ fi
+
+ # NOTE: We don't do a soft-reload via signal, since [global] ksmbd.conf setting changes will be ignored, so always reset hard.
+ [ -e /sys/module/ksmbd ] && stop_service
+
+ [ ! -e /sys/module/ksmbd ] && modprobe ksmbd 2> /dev/null
+ if [ ! -e /sys/module/ksmbd ]; then
+ logger -p daemon.error -t 'ksmbd' "modprobe of ksmbd module failed, can\'t start ksmbd!"
+ exit 1
+ fi
+
+ logger -p daemon.notice -t 'ksmbd' "Starting Ksmbd userspace service."
+ procd_open_instance
+ procd_add_mdns "smb" "tcp" "445" "daemon=ksmbd"
+ procd_set_param command /usr/sbin/ksmbd.mountd --n
+ procd_set_param file /etc/ksmbd/ksmbd.conf
+ procd_set_param limits nofile=16384
+ procd_close_instance
+}
+
+stop_service()
+{
+ logger -p daemon.notice -t 'ksmbd' "Stopping Ksmbd userspace service."
+ killall ksmbd.mountd > /dev/null 2>&1
+
+ [ -e /sys/module/ksmbd ] && rmmod ksmbd > /dev/null 2>&1
+ # kill server if we cant rmmod
+ [ -e /sys/module/ksmbd ] && kill_server
+ # next try
+ [ -e /sys/module/ksmbd ] && rmmod ksmbd > /dev/null 2>&1
+
+ if [ -e /sys/module/ksmbd ]; then
+ logger -p daemon.error -t 'ksmbd' "module still loaded after kill_server?"
+ fi
+ [ -f /tmp/ksmbd.lock ] && rm /tmp/ksmbd.lock
+}
+
+# reload_service() {
+ # restart "$@"
+# }
diff --git a/ksmbd-tools/files/ksmbd.sysctl b/ksmbd-tools/files/ksmbd.sysctl
new file mode 100644
index 000000000..cf7b2d569
--- /dev/null
+++ b/ksmbd-tools/files/ksmbd.sysctl
@@ -0,0 +1,5 @@
+# Bound buffered-write backlog to smooth sustained uploads on flash storage.
+# These VM limits apply system-wide, including writers other than ksmbd.
+# /etc/sysctl.conf is loaded afterwards and can override these values.
+vm.dirty_background_bytes=4194304
+vm.dirty_bytes=16777216
diff --git a/ksmbd-tools/files/shares.sh b/ksmbd-tools/files/shares.sh
new file mode 100644
index 000000000..ba552048e
--- /dev/null
+++ b/ksmbd-tools/files/shares.sh
@@ -0,0 +1,127 @@
+#!/bin/sh
+# Home shares remain private runtime entries; mount shares are stored in UCI.
+ksmbd_share_exists()
+{
+ KSMBD_SHARE_NAME="$1" KSMBD_SHARE_PATH="$2" awk '
+ /^\[/ { name = substr($0, 2, length($0)-2); if (tolower(name) == tolower(ENVIRON["KSMBD_SHARE_NAME"])) found=1 }
+ /^[ \t]*path[ \t]*=/ { path=$0; sub(/^[ \t]*path[ \t]*=[ \t]*/, "", path); if (path == ENVIRON["KSMBD_SHARE_PATH"]) found=1 }
+ END { exit !found }' /var/etc/ksmbd/ksmbd.conf
+}
+
+ksmbd_add_homes()
+{
+ local user password uid gid gecos home shell
+ [ -r /etc/ksmbd/ksmbdpwd.db ] || return 0
+ while IFS=: read -r user password uid gid gecos home shell; do
+ case "$user" in ''|*[!a-zA-Z0-9_.-]*) continue;; esac
+ case "$home" in /|/var|/tmp|/dev|/proc|/sys|'') continue;; /*) ;; *) continue;; esac
+ [ -d "$home" ] || continue
+ # Private homes are only generated for registered SMB users.
+ awk -F: -v user="$user" '$1 == user { found=1 } END { exit !found }' /etc/ksmbd/ksmbdpwd.db || continue
+ ksmbd_share_exists "$user" "$home" && continue
+ {
+ printf '\n[%s]\n\tpath = %s\n' "$user" "$home"
+ printf '\tcomment = Home Directories\n\tvalid users = %s\n' "$user"
+ printf '\tbrowseable = no\n\tguest ok = no\n\tread only = no\n'
+ printf '\tcreate mask = 0750\n\tdirectory mask = 0750\n'
+ } >> /var/etc/ksmbd/ksmbd.conf
+ done < /etc/passwd
+}
+
+# Use a private delta directory so commits do not include pending LuCI edits.
+ksmbd_mount_uci()
+{
+ uci ${UCI_CONFIG_DIR:+-c "$UCI_CONFIG_DIR"} -t "$ksmbd_delta" -q "$@"
+}
+
+ksmbd_prune_mount()
+{
+ local section="$1" path auto_path fingerprint
+ config_get path "$section" path
+ config_get auto_path "$section" auto_path
+ [ -n "$auto_path" ] || return 0
+ if [ "$path" != "$auto_path" ]; then
+ # Editing the path converts an automatic share into a manual share.
+ ksmbd_mount_uci delete "ksmbd.$section.auto_path"
+ elif [ "$KSMBD_AUTOSHARE" -ne 1 ] || ! grep -Fxq "$path" "$ksmbd_mounts"; then
+ ksmbd_mount_uci delete "ksmbd.$section"
+ else
+ # Only remove identical generated entries; differing permissions are
+ # intentional configuration and must not be silently discarded.
+ fingerprint="$(ksmbd_mount_uci show "ksmbd.$section" |
+ sed '1d; s/^ksmbd\.[^.]*\.//' | LC_ALL=C sort | md5sum | cut -d ' ' -f 1)"
+ if grep -Fxq "$fingerprint" "$ksmbd_delta/seen"; then
+ ksmbd_mount_uci delete "ksmbd.$section"
+ else
+ printf '%s\n' "$fingerprint" >> "$ksmbd_delta/seen"
+ fi
+ fi
+ return 0
+}
+
+ksmbd_find_mount()
+{
+ local section="$1" share_name share_path
+ config_get share_name "$section" name
+ config_get share_path "$section" path
+ if [ "$share_path" = "$target" ] ||
+ [ "$(printf '%s' "$share_name" | tr 'A-Z' 'a-z')" = "$(printf '%s' "$name" | tr 'A-Z' 'a-z')" ]; then
+ ksmbd_found=1
+ fi
+}
+
+ksmbd_scan_mounts()
+{
+ local device target fstype options rest name
+ while read -r device target fstype options rest; do
+ case "$device" in /dev/sd*|/dev/hd*|/dev/mmcblk*|/dev/nvme*|/dev/md*) ;; *) continue;; esac
+ # Firmware images and other read-only mounts are not automatic shares.
+ case "$fstype" in squashfs|erofs|cramfs|romfs|iso9660) continue;; esac
+ case ",$options," in *,ro,*) continue;; esac
+ target="$(printf '%b' "$target")"
+ case "$target" in /|/rom|/overlay|/boot|/boot/*|/tmp|/tmp/*|/dev|/dev/*|/proc|/proc/*|/sys|/sys/*) continue;; esac
+ case "$target" in *'
+'*|*' '*) continue;; esac
+ [ -d "$target" ] || continue
+ name="${target##*/}"
+ case "$name" in ''|*'['*|*']'*|*';'*|*'%'*) name="${device##*/}";; esac
+ printf '%s\t%s\tno\n' "$target" "$name"
+ done < /proc/mounts
+}
+
+ksmbd_sync_mounts()
+(
+ local ksmbd_delta ksmbd_mounts target name readonly section ksmbd_found
+ flock -x 8 || return 1
+ # The caller's snapshot can predate another hotplug worker's commit.
+ config_load ksmbd
+ ksmbd_delta="$(mktemp -d /tmp/ksmbd-uci.XXXXXX)" || return 1
+ trap 'rm -rf "$ksmbd_delta"' EXIT
+ : > "$ksmbd_delta/seen"
+ ksmbd_mounts="$ksmbd_delta/paths"
+ ksmbd_scan_mounts > "$ksmbd_delta/mounts"
+ cut -f1 "$ksmbd_delta/mounts" > "$ksmbd_mounts"
+ config_foreach ksmbd_prune_mount share
+ if [ -n "$(ksmbd_mount_uci changes ksmbd)" ]; then
+ ksmbd_mount_uci commit ksmbd || return 1
+ fi
+ [ "$KSMBD_AUTOSHARE" -eq 1 ] || return 0
+ config_load ksmbd
+ while IFS="$(printf '\t')" read -r target name readonly; do
+ ksmbd_found=0
+ config_foreach ksmbd_find_mount share
+ [ "$ksmbd_found" -eq 0 ] || continue
+ section="$(ksmbd_mount_uci add ksmbd share)" || return 1
+ ksmbd_mount_uci set "ksmbd.$section.name=$name"
+ ksmbd_mount_uci set "ksmbd.$section.path=$target"
+ ksmbd_mount_uci set "ksmbd.$section.auto_path=$target"
+ ksmbd_mount_uci set "ksmbd.$section.browseable=yes"
+ ksmbd_mount_uci set "ksmbd.$section.guest_ok=yes"
+ ksmbd_mount_uci set "ksmbd.$section.read_only=$readonly"
+ ksmbd_mount_uci set "ksmbd.$section.force_root=1"
+ ksmbd_mount_uci set "ksmbd.$section.create_mask=0666"
+ ksmbd_mount_uci set "ksmbd.$section.dir_mask=0777"
+ ksmbd_mount_uci commit ksmbd || return 1
+ config_load ksmbd
+ done < "$ksmbd_delta/mounts"
+) 8>/var/lock/ksmbd-autoshare.lock
diff --git a/ksmbd-tools/files/smb.service b/ksmbd-tools/files/smb.service
new file mode 100644
index 000000000..24e643036
--- /dev/null
+++ b/ksmbd-tools/files/smb.service
@@ -0,0 +1,9 @@
+
+
+
+ %h
+
+ _smb._tcp
+ 445
+
+
diff --git a/ksmbd-tools/patches/030-glib.patch b/ksmbd-tools/patches/030-glib.patch
new file mode 100644
index 000000000..9e3fa2c62
--- /dev/null
+++ b/ksmbd-tools/patches/030-glib.patch
@@ -0,0 +1,14 @@
+Subject: [PATCH] Link glib statically for OpenWrt
+
+Signed-off-by: coolsnowwolf
+
+--- a/meson.build
++++ b/meson.build
+@@ -21,6 +21,7 @@
+ glib_dep = dependency(
+ 'glib-2.0',
+ version: '>= 2.58',
++ static: true,
+ )
+ libnl_dep = dependency(
+ 'libnl-genl-3.0',
diff --git a/luci-app-clashoo/Makefile b/luci-app-clashoo/Makefile
index cbd221359..38181dd30 100644
--- a/luci-app-clashoo/Makefile
+++ b/luci-app-clashoo/Makefile
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-clashoo
PKG_VERSION:=1.31.0
-PKG_RELEASE:=7
+PKG_RELEASE:=8
PKG_MAINTAINER:=kenzok8
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
diff --git a/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo b/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo
index d89be1a98..52a2ceae5 100755
--- a/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo
+++ b/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo
@@ -2,9 +2,18 @@
'use strict';
-import { access, popen, glob, readfile, writefile, stat, lstat, unlink, open, mkdtemp, rename, rmdir } from 'fs';
+import { access, popen, glob, readfile, writefile, stat, lstat, unlink, open, rename, rmdir } from 'fs';
import { cursor } from 'uci';
+// 兼容 ucode < 2026-10-10:原生缺少 mkdtemp,改用 mktemp -d
+function mkdtemp(tpl) {
+ let p = popen("mktemp -d '" + tpl + "' 2>/dev/null");
+ if (!p) return null;
+ let r = trim(p.read('all') || '');
+ p.close();
+ return r || null;
+}
+
const BINARY_PATHS = [
'/usr/bin/mihomo-stable',
'/usr/bin/mihomo',
@@ -1219,6 +1228,10 @@ if (!t) t = 'template';
return '_merged_' + s + '__' + t + '.yaml';
}
+function is_generated_yaml(path) {
+return system("grep -q '^x-clashoo:' " + shell_quote(path) + " 2>/dev/null") == 0;
+}
+
function template_search_dirs() {
return [TEMPLATE_DIR, '/usr/share/clashoo/config/template'];
}
@@ -1228,7 +1241,7 @@ name = ensure_yaml_name(name || '');
if (!name) return '';
for (let dir in template_search_dirs()) {
let p = dir + '/' + name;
-if (access(p, 'r')) return p;
+if (access(p, 'r') && !is_generated_yaml(p)) return p;
}
return '';
}
@@ -2907,6 +2920,7 @@ for (let dir in template_search_dirs()) {
for (let f in (glob(dir + '/*.yaml') || [])) {
let name = replace(f, dir + '/', '');
if (index(name, '_merged_') == 0 || seen[name]) continue;
+if (is_generated_yaml(f)) continue;
seen[name] = 1;
let fi = stat(f);
push(files, { name, mtime: fi ? ('' + fi.mtime) : null, size: fi ? (int(fi.size / 1024) + ' KB') : null });
@@ -2914,6 +2928,7 @@ push(files, { name, mtime: fi ? ('' + fi.mtime) : null, size: fi ? (int(fi.size
for (let f in (glob(dir + '/*.yml') || [])) {
let name = replace(f, dir + '/', '');
if (index(name, '_merged_') == 0 || seen[name]) continue;
+if (is_generated_yaml(f)) continue;
seen[name] = 1;
let fi = stat(f);
push(files, { name, mtime: fi ? ('' + fi.mtime) : null, size: fi ? (int(fi.size / 1024) + ' KB') : null });
@@ -3477,13 +3492,22 @@ if (!tpl_src)
return { success: false, error: 'invalid_template', message: '模板来源不能为空' };
let pairs = [];
+let names = {};
try {
let parsed = json(pairs_raw || '[]');
-if (type(parsed) == 'array') {
+if (type(parsed) != 'array')
+return { success: false, error: 'invalid_pairs', message: '订阅参数格式错误' };
for (let p in parsed) {
-if (p && p.name && p.url && match(p.url, /^https?:\/\//))
-push(pairs, { name: trim(p.name), url: trim(p.url) });
-}
+if (!p || type(p.name) != 'string' || type(p.url) != 'string')
+return { success: false, error: 'invalid_pairs', message: '订阅名称和链接不能为空' };
+let name = trim(p.name);
+let url = trim(p.url);
+if (!name || !match(url, /^https?:\/\//))
+return { success: false, error: 'invalid_pairs', message: '订阅名称不能为空,链接必须以 http:// 或 https:// 开头' };
+if (names[name])
+return { success: false, error: 'duplicate_provider_name', message: '订阅名称不能重复' };
+names[name] = 1;
+push(pairs, { name, url });
}
} catch (err) {
return { success: false, error: 'invalid_pairs', message: '订阅参数格式错误' };
@@ -3507,11 +3531,7 @@ if (system(dl_cmd) != 0 || !access(tmp_tpl, 'r'))
return { success: false, error: 'download_failed', message: '远程模板下载失败' };
} else {
let safe = safe_name(tpl_src);
-let local_path = null;
-let search_dirs = template_search_dirs();
-for (let i = 0; i < length(search_dirs); i++) {
-if (access(search_dirs[i] + '/' + safe, 'r')) { local_path = search_dirs[i] + '/' + safe; break; }
-}
+let local_path = find_template_path(safe);
if (!local_path)
return { success: false, error: 'template_not_found', message: '本地模板文件不存在' };
if (system('cp -f ' + shell_quote(local_path) + ' ' + shell_quote(tmp_tpl)) != 0)
@@ -3528,17 +3548,27 @@ return { success: false, error: 'missing_yq', message: '缺少 yq,无法注入
system("cp -f " + shell_quote(tmp_tpl) + " " + shell_quote(exploded));
}
+ /* 模板必须有 proxy-providers 段 */
+ if (system("yq -e 'has(\"proxy-providers\")' " + shell_quote(exploded) + " >/dev/null 2>&1") != 0) {
+ system("rm -f " + shell_quote(exploded));
+ return { success: false, error: 'no_proxy_providers', message: '模板缺少 proxy-providers 段' };
+ }
+ if (system("yq -e '(.\"proxy-providers\" | tag) == \"!!map\" and (.\"proxy-providers\" | length) == 1 and (.\"proxy-providers\" | to_entries[0].value | tag) == \"!!map\" and (.\"proxy-providers\" | to_entries[0].value.type) == \"http\"' " + shell_quote(exploded) + " >/dev/null 2>&1") != 0) {
+ system("rm -f " + shell_quote(exploded));
+ return { success: false, error: 'invalid_proxy_providers', message: '面板模板只支持一个 type: http 的样板 provider;多 provider 配置请直接上传使用' };
+ }
+
/* pairs 写到临时 JSON 文件 */
writefile(tmp_pairs, sprintf('%.J', pairs));
let tpl_label = match(tpl_src, /^https?:\/\//) ? tpl_src : replace(tpl_src, /^.*\//, "");
+ let tpl_expr = length(pairs) > 1 ? '($entry.value | del(.path))' : '$entry.value';
let yq_expr = '(load("' + tmp_pairs + '")) as $pairs | '
- + '(.["proxy-providers"] | keys) as $oldkeys | '
- + '($oldkeys | to_entries | map(.key as $i | .value as $k | {"key": $k, "value": ($pairs[$i].name // $k)}) | from_entries) as $ren | '
- + '($oldkeys | to_entries | map(select(.key < ($pairs | length)) | .key as $i | .value as $k | {"key": $k, "value": $pairs[$i].url}) | from_entries) as $reurl | '
- + '.["proxy-providers"] |= with_entries(.key as $old | .key = ($ren[$old] // $old) | .value.url = ($reurl[$old] // .value.url)) | '
- + '(.["proxy-groups"][] | select(has("use")) | .use) |= map($ren[.] // .) | '
+ + '(.["proxy-providers"] | to_entries[0]) as $entry | '
+ + tpl_expr + ' as $tpl | '
+ + '.["proxy-providers"] = ($pairs | map({"key": .name, "value": ($tpl + {"url": .url})}) | from_entries) | '
+ + '(.["proxy-groups"][] | select(has("use")) | .use) |= (map(. as $name | ([$pairs[].name] | select($name == $entry.key)) // [$name]) | flatten) | '
+ '."x-clashoo" = {"template": strenv(TPL_LABEL)}';
let inject_cmd = "TPL_LABEL=" + shell_quote(tpl_label)
@@ -3584,7 +3614,7 @@ system('rm -f ' + shell_quote(tmp_tpl));
}
}
if (_core_bin) {
- let _ret = system(_core_bin + " -t -d /etc/clashoo -f " + shell_quote(out_path) + " 2>/tmp/clashoo_tpl_verify.txt");
+ let _ret = system(_core_bin + " -t -d /etc/clashoo -f " + shell_quote(out_path) + " >/dev/null 2>/tmp/clashoo_tpl_verify.txt");
if (_ret != 0) {
let _err = trim(readfile('/tmp/clashoo_tpl_verify.txt') || '配置校验失败');
system('rm -f ' + shell_quote(out_path) + ' /tmp/clashoo_tpl_verify.txt 2>/dev/null');
diff --git a/luci-app-diskman/Makefile b/luci-app-diskman/Makefile
index 5e0f7d5bc..20abf875d 100644
--- a/luci-app-diskman/Makefile
+++ b/luci-app-diskman/Makefile
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-diskman
PKG_VERSION:=0.2.13
-PKG_RELEASE:=2
+PKG_RELEASE:=3
PKG_MAINTAINER:=lisaac
PKG_LICENSE:=AGPL-3.0
@@ -24,7 +24,7 @@ define Package/luci-app-diskman/config
config PACKAGE_$(PKG_NAME)_INCLUDE_btrfs_progs
depends on PACKAGE_$(PKG_NAME)
bool "Include btrfs-progs"
- default y
+ default n
config PACKAGE_$(PKG_NAME)_INCLUDE_lsblk
depends on PACKAGE_$(PKG_NAME)
diff --git a/luci-app-diskman/luasrc/model/cbi/diskman/disks.lua b/luci-app-diskman/luasrc/model/cbi/diskman/disks.lua
index 3382afff8..a5b2a7e15 100644
--- a/luci-app-diskman/luasrc/model/cbi/diskman/disks.lua
+++ b/luci-app-diskman/luasrc/model/cbi/diskman/disks.lua
@@ -43,7 +43,7 @@ d:option(DummyValue, "temp", translate("Temp"))
d:option(DummyValue, "p_table", translate("Partition Table"))
d:option(DummyValue, "sata_ver", translate("SATA Version"))
-- d:option(DummyValue, "rota_rate", translate("Rotation Rate"))
-d:option(DummyValue, "health_status", translate("Health") .. "
" .. translate("Status"))
+d:option(DummyValue, "health_status", translate("Health") .. " / " .. translate("Status"))
-- d:option(DummyValue, "status", translate("Status"))
-- local btn_eject = d:option(Button, "_eject")
diff --git a/luci-app-diskman/luasrc/model/cbi/diskman/partition.lua b/luci-app-diskman/luasrc/model/cbi/diskman/partition.lua
index 348a617a6..179fd90f2 100644
--- a/luci-app-diskman/luasrc/model/cbi/diskman/partition.lua
+++ b/luci-app-diskman/luasrc/model/cbi/diskman/partition.lua
@@ -25,6 +25,25 @@ m.reset = false
local disk_info = dm.get_disk_info(dev, true)
local format_cmd = dm.get_format_cmd()
+local function is_firmware_partition(part)
+ local name = part.partition_name or ""
+ return name:match("^0:") or name:find("rootfs", 1, true)
+end
+
+local first_partition_start
+for _, part in ipairs(disk_info.partitions) do
+ local start = tonumber(part.sec_start)
+ if part.number > 0 and start then
+ first_partition_start = math.min(first_partition_start or start, start)
+ end
+end
+
+local function is_leading_free_space(part)
+ -- Bootloader data may live in the gap before the first real partition.
+ return part.number == -1 and first_partition_start and
+ tonumber(part.sec_end) < first_partition_start
+end
+
s = m:section(Table, {disk_info}, translate("Device Info"))
-- s:option(DummyValue, "key")
-- s:option(DummyValue, "value")
@@ -118,14 +137,15 @@ end
-- partitions info
if not disk_info.p_table:match("Raid") then
- s_partition_table = m:section(Table, disk_info.partitions, translate("Partitions Info"), translate("Default 2048 sector alignment, support +size{b,k,m,g,t} in End Sector"))
+ s_partition_table = m:section(Table, disk_info.partitions, translate("Partitions Info"), translate("Default 1 MiB alignment, support +size{b,k,m,g,t} in End Sector"))
-- s_partition_table:option(DummyValue, "number", translate("Number"))
s_partition_table:option(DummyValue, "name", translate("Name"))
+ s_partition_table:option(DummyValue, "partition_name", translate("Partition Name"))
local val_sec_start = s_partition_table:option(Value, "sec_start", translate("Start Sector"))
val_sec_start.render = function(self, section, scope)
-- could create new partition
- if disk_info.partitions[section].number == -1 and disk_info.partitions[section].size > 1 * 1024 * 1024 then
+ if disk_info.partitions[section].number == -1 and not is_leading_free_space(disk_info.partitions[section]) and disk_info.partitions[section].size > 1 * 1024 * 1024 then
self.template = "cbi/value"
Value.render(self, section, scope)
else
@@ -136,7 +156,7 @@ if not disk_info.p_table:match("Raid") then
local val_sec_end = s_partition_table:option(Value, "sec_end", translate("End Sector"))
val_sec_end.render = function(self, section, scope)
-- could create new partition
- if disk_info.partitions[section].number == -1 and disk_info.partitions[section].size > 1 * 1024 * 1024 then
+ if disk_info.partitions[section].number == -1 and not is_leading_free_space(disk_info.partitions[section]) and disk_info.partitions[section].size > 1 * 1024 * 1024 then
self.template = "cbi/value"
Value.render(self, section, scope)
else
@@ -192,8 +212,9 @@ if not disk_info.p_table:match("Raid") then
disk_info.partitions[section]._fs = value
end
val_fs.render = function(self, section, scope)
- -- use listvalue when partition not mounted
- if disk_info.partitions[section].mount_point == "-" and disk_info.partitions[section].number ~= -1 and disk_info.partitions[section].type ~= "extended" then
+ local part = disk_info.partitions[section]
+ -- Keep firmware partitions read-only in the filesystem column.
+ if not is_firmware_partition(part) and part.mount_point == "-" and part.number ~= -1 and part.type ~= "extended" then
self.template = "diskman/cbi/format_button"
self.inputstyle = "reset"
self.inputtitle = disk_info.partitions[section].fs == "raw" and translate("Format") or disk_info.partitions[section].fs
@@ -261,6 +282,12 @@ if not disk_info.p_table:match("Raid") then
btn_action.forcewrite = true
btn_action.template = "diskman/cbi/disabled_button"
btn_action.render = function(self, section, scope)
+ if is_firmware_partition(disk_info.partitions[section]) or is_leading_free_space(disk_info.partitions[section]) then
+ self.template = "cbi/dvalue"
+ DummyValue.render(self, section, scope)
+ return
+ end
+ self.template = "diskman/cbi/disabled_button"
-- if partition is mounted or the size < 1mb, then disable the add action
if disk_info.partitions[section].mount_point ~= "-" or (disk_info.partitions[section].type ~= "extended" and disk_info.partitions[section].number == -1 and disk_info.partitions[section].size <= 1 * 1024 * 1024) then
self.view_disabled = true
@@ -282,16 +309,17 @@ if not disk_info.p_table:match("Raid") then
Button.render(self, section, scope)
end
btn_action.write = function(self, section, value)
+ if is_firmware_partition(disk_info.partitions[section]) or is_leading_free_space(disk_info.partitions[section]) then return end
if value == translate("New") then
local start_sec = disk_info.partitions[section]._sec_start and tonumber(disk_info.partitions[section]._sec_start) or tonumber(disk_info.partitions[section].sec_start)
local end_sec = disk_info.partitions[section]._sec_end
+ local logic_sec = tonumber(disk_info.logic_sec)
+ local align = math.max(math.ceil(tonumber(disk_info.phy_sec) / logic_sec), math.ceil(1048576 / logic_sec))
if start_sec then
-- for sector alignment
- local align = tonumber(disk_info.phy_sec) / tonumber(disk_info.logic_sec)
- align = (align < 2048) and 2048
- if start_sec < 2048 then
- start_sec = "2048" .. "s"
+ if start_sec < align then
+ start_sec = tostring(align) .. "s"
elseif math.fmod( start_sec, align ) ~= 0 then
start_sec = tostring(start_sec + align - math.fmod( start_sec, align )) .. "s"
else
@@ -306,14 +334,14 @@ if not disk_info.p_table:match("Raid") then
if tonumber(end_size) and end_unit then
local unit ={
B=1,
- S=512,
+ S=tonumber(disk_info.logic_sec),
K=1024,
M=1048576,
G=1073741824,
T=1099511627776
}
end_unit = end_unit:upper()
- end_sec = tostring(tonumber(end_size) * unit[end_unit] / unit["S"] + tonumber(start_sec:sub(1,-2)) - 1 ) .. "s"
+ end_sec = tostring(math.ceil(tonumber(end_size) * unit[end_unit] / logic_sec) + tonumber(start_sec:sub(1,-2)) - 1) .. "s"
elseif tonumber(end_sec) then
end_sec = end_sec .. "s"
else
@@ -325,8 +353,8 @@ if not disk_info.p_table:match("Raid") then
if disk_info.p_table == "MBR" and disk_info["extended_partition_index"] then
if tonumber(disk_info.partitions[disk_info["extended_partition_index"]].sec_start) <= tonumber(start_sec:sub(1,-2)) and tonumber(disk_info.partitions[disk_info["extended_partition_index"]].sec_end) >= tonumber(end_sec:sub(1,-2)) then
part_type = "logical"
- if tonumber(start_sec:sub(1,-2)) - tonumber(disk_info.partitions[section].sec_start) < 2048 then
- start_sec = tonumber(start_sec:sub(1,-2)) + 2048
+ if tonumber(start_sec:sub(1,-2)) - tonumber(disk_info.partitions[section].sec_start) < align then
+ start_sec = tonumber(start_sec:sub(1,-2)) + align
start_sec = start_sec .."s"
end
end
@@ -363,4 +391,4 @@ if not disk_info.p_table:match("Raid") then
end
end
-return m
\ No newline at end of file
+return m
diff --git a/luci-app-diskman/luasrc/model/diskman.lua b/luci-app-diskman/luasrc/model/diskman.lua
index ec3d60cd2..96e85f52b 100644
--- a/luci-app-diskman/luasrc/model/diskman.lua
+++ b/luci-app-diskman/luasrc/model/diskman.lua
@@ -9,6 +9,7 @@ local ver = require "luci.version"
local CMD = {"parted", "mdadm", "blkid", "smartctl", "df", "btrfs", "lsblk"}
local d = {command ={}}
+local SYSFS_SECTOR_SIZE = 512
for _, cmd in ipairs(CMD) do
local command = luci.sys.exec("/usr/bin/which " .. cmd)
d.command[cmd] = command:match("^.+"..cmd) or nil
@@ -33,7 +34,42 @@ function byte_format(byte)
end
end
+local function get_emmc_health(device)
+ if not device:match("^mmcblk%d+$") then return end
+ local path = "/sys/class/block/" .. device .. "/device/"
+ if not (nixio.fs.readfile(path .. "type") or ""):match("^MMC%s*$") then return end
+ local translate = require("luci.i18n").translate
+ local info = {}
+ local lifetime
+ -- Use the more worn of the supported A/B estimates; zero means undefined.
+ for value in (nixio.fs.readfile(path .. "life_time") or ""):gmatch("%S+") do
+ local estimate = tonumber(value)
+ if estimate and estimate >= 1 and estimate <= 11 then
+ lifetime = math.max(lifetime or 0, estimate)
+ end
+ end
+ if lifetime then
+ if lifetime == 11 then
+ info.health = translate("Estimated lifetime exceeded")
+ else
+ -- Report estimated remaining life as a range, not an exact percentage.
+ info.health = string.format("%d-%d%%", 100 - lifetime * 10, 110 - lifetime * 10)
+ end
+ end
+ local eol = tonumber(nixio.fs.readfile(path .. "pre_eol_info") or "")
+ if eol == 1 then
+ info.status = translate("Normal")
+ elseif eol == 2 then
+ info.status = translate("Warning")
+ elseif eol == 3 then
+ info.status = translate("Urgent")
+ end
+ return info
+end
+
local get_smart_info = function(device)
+ local emmc_info = get_emmc_health(device)
+ if emmc_info then return emmc_info end
local section
local smart_info = {}
for _, line in ipairs(luci.util.execl(d.command.smartctl .. " -H -A -i -n standby -f brief /dev/" .. device)) do
@@ -188,6 +224,10 @@ local get_parted_info = function(device)
elseif device:match("mmcblk") or device:match("md") or device:match("nvme") then
partition_temp["name"] = device.."p"..partition_temp["number"]
end
+ -- Read the kernel partition name: older parted output does not escape colons.
+ local uevent = partition_temp["name"] ~= "-" and
+ nixio.fs.readfile("/sys/class/block/" .. partition_temp["name"] .. "/uevent") or ""
+ partition_temp["partition_name"] = (uevent or ""):match("\nPARTNAME=([^\n]+)") or "-"
if partition_temp["number"] > 0 and partition_temp["fs"] == "" and d.command.lsblk then
partition_temp["fs"] = (luci.util.exec(d.command.lsblk .. " /dev/" .. partition_temp["name"] .. " -no fstype") or ""):match("([^%s]+)") or ""
end
@@ -230,7 +270,7 @@ local get_parted_info = function(device)
elseif (p["number"] <= 4) and (p["number"] > 0) then
local s = nixio.fs.readfile("/sys/block/"..device.."/"..p["name"].."/size")
if s then
- local real_size_sec = tonumber(s) * tonumber(disk_temp.logic_sec)
+ local real_size_sec = tonumber(s) * SYSFS_SECTOR_SIZE
-- if size not equal, it's an extended
if real_size_sec ~= p["size"] then
disk_temp["extended_partition_index"] = i
@@ -313,7 +353,7 @@ d.get_disk_info = function(device, wakeup)
-- if only weakup == true, weakup the disk and check parted_info.
if smart_info.status ~= "STANDBY" or wakeup or (smart_info["p_table"] and not smart_info["p_table"]:match("Raid")) or device:match("^md") then
disk_info = get_parted_info(device)
- disk_info["sec_size"] = disk_info["logic_sec"] .. "/" .. disk_info["phy_sec"]
+ disk_info["sec_size"] = tostring(disk_info["logic_sec"] or "-") .. "/" .. tostring(disk_info["phy_sec"] or "-")
disk_info["size_formated"] = byte_format(tonumber(disk_info["size"]))
-- if status is standby, after get part info, the disk is weakuped, then get smart_info again for more informations
if smart_info.status ~= "ACTIVE" then smart_info = get_smart_info(device) end
@@ -362,11 +402,9 @@ d.list_raid_devices = function()
end
local size = tonumber(fs.readfile(string.format("/sys/class/block/%s/size", mdpath)))
- local ss = tonumber(fs.readfile(string.format("/sys/class/block/%s/queue/logical_block_size", mdpath)))
-
device_info["path"] = "/dev/"..mdpath
- device_info["size"] = size*ss
- device_info["size_formated"] = byte_format(size*ss)
+ device_info["size"] = size * SYSFS_SECTOR_SIZE
+ device_info["size_formated"] = byte_format(device_info["size"])
device_info["active"] = active:upper()
device_info["level"] = level
device_info["members"] = members
@@ -418,19 +456,18 @@ d.list_devices = function()
local device_info = {}
local device = "/dev/" .. bname
local size = tonumber(fs.readfile(string.format("/sys/class/block/%s/size", bname)) or "0")
- local ss = tonumber(fs.readfile(string.format("/sys/class/block/%s/queue/logical_block_size", bname)) or "0")
local model = fs.readfile(string.format("/sys/class/block/%s/device/model", bname))
local partitions = {}
for part in nixio.fs.glob("/sys/block/" .. bname .."/" .. bname .. "*") do
local pname = nixio.fs.basename(part)
- local psize = byte_format(tonumber(nixio.fs.readfile(part .. "/size"))*ss)
+ local psize = byte_format(tonumber(nixio.fs.readfile(part .. "/size")) * SYSFS_SECTOR_SIZE)
local mount_point = get_mount_point(pname)
if mount_point then device_info["inuse"] = true end
table.insert(partitions, {name = pname, size_formated = psize, inuse = mount_point})
end
device_info["path"] = device
- device_info["size_formated"] = byte_format(size*ss)
+ device_info["size_formated"] = byte_format(size * SYSFS_SECTOR_SIZE)
device_info["model"] = model
device_info["partitions"] = partitions
-- true or false
diff --git a/luci-app-diskman/luasrc/view/diskman/disk_info.htm b/luci-app-diskman/luasrc/view/diskman/disk_info.htm
index 10ba3e7c2..287c78fcc 100644
--- a/luci-app-diskman/luasrc/view/diskman/disk_info.htm
+++ b/luci-app-diskman/luasrc/view/diskman/disk_info.htm
@@ -1,7 +1,66 @@
+