diff --git a/kvmapp/system/init.d/S03usbdev b/kvmapp/system/init.d/S03usbdev index f6cd9cf..8717441 100755 --- a/kvmapp/system/init.d/S03usbdev +++ b/kvmapp/system/init.d/S03usbdev @@ -94,6 +94,7 @@ start_usb_dev(){ ln -s functions/hid.GS2 configs/c.1 fi + # dedicated for media if [ -e /boot/usb.disk0 ] then mkdir functions/mass_storage.disk0 @@ -108,17 +109,20 @@ start_usb_dev(){ disk=$(cat /boot/usb.disk0) if [ -z "${disk}" ] then - # if [ ! -e /mnt/usbdisk.img ] - # then - # fallocate -l 8G /mnt/usbdisk.img - # mkfs.vfat /mnt/usbdisk.img - # fi - echo /dev/mmcblk0p3 > functions/mass_storage.disk0/lun.0/file + echo "" > functions/mass_storage.disk0/lun.0/file else cat /boot/usb.disk0 > functions/mass_storage.disk0/lun.0/file fi fi + # mount data partition + if [ -e /boot/usb.disk1 ] + then + mkdir functions/mass_storage.disk1 + ln -s functions/mass_storage.disk1 configs/c.1/ + echo /dev/mmcblk0p3 > functions/mass_storage.disk1/lun.0/file + fi + ls /sys/class/udc/ | cat > UDC echo device > /proc/cviusb/otg_role } diff --git a/server/proto/vm.go b/server/proto/vm.go index 21d3844..3c56533 100644 --- a/server/proto/vm.go +++ b/server/proto/vm.go @@ -57,6 +57,7 @@ type DeleteScriptReq struct { type GetVirtualDeviceRsp struct { Network bool `json:"network"` + Media bool `json:"media"` Disk bool `json:"disk"` } diff --git a/server/service/storage/image.go b/server/service/storage/image.go index 4e0be46..a42cb07 100644 --- a/server/service/storage/image.go +++ b/server/service/storage/image.go @@ -18,11 +18,11 @@ import ( const ( imageDirectory = "/data" - imageNone = "/dev/mmcblk0p3" cdromFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/cdrom" mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file" inquiryString = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/inquiry_string" roFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/ro" + usbNoRstMarker = "/boot/usb.norst" ) func (s *Service) GetImages(c *gin.Context) { @@ -108,16 +108,20 @@ func (s *Service) MountImage(c *gin.Context) { return } - // mount + // mount if file provided image := req.File - if image == "" { - image = imageNone + if image != "" { + if err := os.WriteFile(mountDevice, []byte(image), 0o666); err != nil { + log.Errorf("mount file %s failed: %s", image, err) + rsp.ErrRsp(c, -2, "mount image failed") + return + } } - if err := os.WriteFile(mountDevice, []byte(image), 0o666); err != nil { - log.Errorf("mount file %s failed: %s", image, err) - rsp.ErrRsp(c, -2, "mount image failed") - return + // check to see if usb gadget reset is disabled + resetUsb := true + if _, err := os.Stat(usbNoRstMarker); err == nil { + resetUsb = false } h := hid.GetHid() @@ -129,18 +133,20 @@ func (s *Service) MountImage(c *gin.Context) { }() // reset usb - commands := []string{ - "echo > /sys/kernel/config/usb_gadget/g0/UDC", - "ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC", - } - - for _, command := range commands { - err := exec.Command("sh", "-c", command).Run() - if err != nil { - rsp.ErrRsp(c, -2, "execute command failed") - return + if resetUsb { + commands := []string{ + "echo > /sys/kernel/config/usb_gadget/g0/UDC", + "ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC", + } + + for _, command := range commands { + err := exec.Command("sh", "-c", command).Run() + if err != nil { + rsp.ErrRsp(c, -2, "execute command failed") + return + } + time.Sleep(100 * time.Millisecond) } - time.Sleep(100 * time.Millisecond) } rsp.OkRsp(c) @@ -157,9 +163,6 @@ func (s *Service) GetMountedImage(c *gin.Context) { } image := strings.ReplaceAll(string(content), "\n", "") - if image == imageNone { - image = "" - } data := &proto.GetMountedImageRsp{ File: image, diff --git a/server/service/vm/virtual-device.go b/server/service/vm/virtual-device.go index 2eb40e9..3804526 100644 --- a/server/service/vm/virtual-device.go +++ b/server/service/vm/virtual-device.go @@ -14,7 +14,8 @@ import ( const ( virtualNetwork = "/boot/usb.rndis0" - virtualDisk = "/boot/usb.disk0" + virtualMedia = "/boot/usb.disk0" + virtualDisk = "/boot/usb.disk1" ) var ( @@ -31,16 +32,29 @@ var ( "/etc/init.d/S03usbdev start", } - mountDiskCommands = []string{ + mountMediaCommands = []string{ "touch /boot/usb.disk0", "/etc/init.d/S03usbdev stop", "/etc/init.d/S03usbdev start", } + unmountMediaCommands = []string{ + "/etc/init.d/S03usbdev stop", + "rm -rf /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk0", + "rm /boot/usb.disk0", + "/etc/init.d/S03usbdev start", + } + + mountDiskCommands = []string{ + "touch /boot/usb.disk1", + "/etc/init.d/S03usbdev stop", + "/etc/init.d/S03usbdev start", + } + unmountDiskCommands = []string{ "/etc/init.d/S03usbdev stop", - "rm -rf /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk0", - "rm /boot/usb.disk0", + "rm -rf /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk1", + "rm /boot/usb.disk1", "/etc/init.d/S03usbdev start", } ) @@ -49,10 +63,12 @@ func (s *Service) GetVirtualDevice(c *gin.Context) { var rsp proto.Response network, _ := isDeviceExist(virtualNetwork) + media, _ := isDeviceExist(virtualMedia) disk, _ := isDeviceExist(virtualDisk) rsp.OkRspWithData(c, &proto.GetVirtualDeviceRsp{ Network: network, + Media: media, Disk: disk, }) log.Debugf("get virtual device success") @@ -80,6 +96,15 @@ func (s *Service) UpdateVirtualDevice(c *gin.Context) { } else { commands = unmountNetworkCommands } + case "media": + device = virtualMedia + + exist, _ := isDeviceExist(device) + if !exist { + commands = mountMediaCommands + } else { + commands = unmountMediaCommands + } case "disk": device = virtualDisk diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index 286b692..d4711f5 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -279,7 +279,9 @@ const en = { }, hidOnly: 'HID-Only Mode', disk: 'Virtual Disk', - diskDesc: 'Mount virtual U-disk on the remote host', + diskDesc: 'Mount SD card on the remote host', + media: "Virtual Image", + mediaDesc: 'Attach virtual image device to the remote host', network: 'Virtual Network', networkDesc: 'Mount virtual network card on the remote host', reboot: 'Reboot', diff --git a/web/src/pages/desktop/menu/settings/device/virtual-devices.tsx b/web/src/pages/desktop/menu/settings/device/virtual-devices.tsx index e378223..ffe2ad0 100644 --- a/web/src/pages/desktop/menu/settings/device/virtual-devices.tsx +++ b/web/src/pages/desktop/menu/settings/device/virtual-devices.tsx @@ -8,8 +8,9 @@ export const VirtualDevices = () => { const { t } = useTranslation(); const [isNetworkOn, setIsNetworkOn] = useState(false); + const [isMediaOn, setIsMediaOn] = useState(false); const [isDiskOn, setIsDiskOn] = useState(false); - const [loading, setLoading] = useState(''); // '' | 'network' | 'disk' + const [loading, setLoading] = useState(''); // '' | 'network' | 'media' | 'disk' useEffect(() => { api.getVirtualDevice().then((rsp) => { @@ -18,11 +19,12 @@ export const VirtualDevices = () => { } setIsNetworkOn(rsp.data.network); + setIsMediaOn(rsp.data.media); setIsDiskOn(rsp.data.disk); }); }, []); - function update(device: 'network' | 'disk') { + function update(device: 'network' | 'media' | 'disk') { if (loading) return; setLoading(device); @@ -33,10 +35,16 @@ export const VirtualDevices = () => { return; } - if (device === 'network') { - setIsNetworkOn(rsp.data.on); - } else { - setIsDiskOn(rsp.data.on); + switch (device) { + case 'network': + setIsNetworkOn(rsp.data.on); + break; + case 'media': + setIsMediaOn(rsp.data.on); + break; + case 'disk': + setIsDiskOn(rsp.data.on); + break; } }) .finally(() => { @@ -46,6 +54,15 @@ export const VirtualDevices = () => { return ( <> +