Skip to main content
Topic: Remove USB storage like a caveman (Read 376 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

Remove USB storage like a caveman

No requirement or problem, just curious!

Trying to do what udisks does, without using udisks. I know nothing about hardware things and would like to understand better what happens in my experiments.
Searching the web, this question has come up several times over the years and it baffles me how it usually seems to remain unanswered and how there is no easier method?

Questions:
  • Are my steps below on the right track? Is there better options? A dangerous mistake?
  • How do we verify the results of the device buffer sync calls?
  • See the dump below: Why does hdparm -Y seem to succeed, while the results of sdparm --command=stop seem unexpected in my case? Despite sdparm using the correct(?) ioctl flags?
  • Is writing directly to /sys/block/sda/device/delete recommended? Does it utilize any internal safeguards?

Looking at this function:
udisks/udiskslinuxdrive.c:handle_power_off

I gather these steps:
  • Whine on drive in use
  • fsync
  • ioctl SCSI SYNCHRONIZE CACHE
  • ioctl SCSI START STOP UNIT
  • Write '1' to /sys/block/.../device/remove

Which aligns with the manual:

Code: [Select]
$ man udisksctl:
    power-off
        Arranges for the drive to be safely removed and powered off. On the OS side this includes ensuring that no process is using the drive, then requesting that in-flight buffers and caches are committed to stable storage. The exact steps for powering off the drive depends on the drive itself and the interconnect used. For drives connected through USB, the effect is that the USB device will be deconfigured followed by disabling the upstream hub port it is connected to.

        Note that as some physical devices contain multiple drives (for example 4-in-1 flash card reader USB devices) powering off one drive may affect other drives. As such there are not a lot of guarantees associated with performing this action. Usually the effect is that the drive disappears as if it was unplugged.

Trying to replicate the udisks steps, I came up with:

  • # eject /dev/sda
  • sync dance
  • # sdparm --command=sync /dev/sda // Verify result?
            or hdparm -f /dev/sda // Verify result?
            or hdparm -F /dev/sda // Verify result?
  • # hdparm -Y /dev/sda
            or sdparm --command=stop /dev/sda // Spins down, then restarts?
            or sdparm --command=eject /dev/sda // Does nothing?
  • # printf '1' > /sys/block/sda/device/delete


Step 1.
AFAIK 'eject' should sync, then unmount all partitions?


Step 2.
The myth as passed down from our elders.

Looking at hdparm -f:

hdparm/hdparm.c:
Code: [Select]
static void flush_buffer_cache (int fd)
{
    sync();
    fsync(fd);              /* flush buffers */
    fdatasync(fd);              /* flush buffers */
    sync();
    if (ioctl(fd, BLKFLSBUF, NULL))     /* do it again, big time */
        perror("BLKFLSBUF failed");
    else
        do_drive_cmd(fd, NULL, 0);  /* IDE: await completion */
    sync();
}


Step 3.
hdparm and sdparm finally call ioctl, but hdparm with different flags.

udisks/udiskslinuxdrive.c:
Code: [Select]
cdb[0] = 0x35;                        /* OPERATION CODE: SYNCHRONIZE CACHE (10) */

sdparm/lib/sg_cmds_basic2.c:
Code: [Select]
#define SYNCHRONIZE_CACHE_CMD     0x35

sdparm/lib/sg_pt_linux.c:do_scsi_pt_v3
    if (ioctl(fd, SG_IO, &v3_hdr) < 0) {

hdparm/sgio.h:
Code: [Select]
ATA_OP_FLUSHCACHE       = 0xe7,
ATA_OP_FLUSHCACHE_EXT       = 0xea,

hdparm/sgio.c:do_drive_cmd
Code: [Select]
#ifdef SG_IO
    rc = sg16(fd, SG_READ, is_dma(tf.command), &tf, data, data_bytes, timeout_secs);
    use_legacy_ioctl:
#endif /* SG_IO */
    return ioctl(fd, HDIO_DRIVE_CMD, args);

hdparm/sgio.c:sg16
Code: [Select]
    if (ioctl(fd, SG_IO, &io_hdr) == -1) {

I set my eyes on hdparm and sdparm because:
Code: [Select]
$ info ioctl
   Although some such objects such as sockets and terminals (1) have
special functions of their own, it would not be practical to create
functions for all these cases.

   Instead these minor operations, known as IOCTLs, are assigned code
numbers and multiplexed through the ioctl function, defined in
sys/ioctl.h’.  The code numbers themselves are defined in many
different headers.

 -- Function: int ioctl (int FILEDES, int COMMAND, ...)


Step 4.
Like above, sdparm uses the flags udisks does, hdparm is different:

udisks/udiskslinuxdrive.c:
Code: [Select]
cdb[0] = 0x1b;                        /* OPERATION CODE: START STOP UNIT */

sdparm/lib/sg_cmds_basic2.c:
Code: [Select]
#define START_STOP_CMD          0x1b

hdparm/sgio.h:
Code: [Select]
ATA_OP_SLEEPNOW2        = 0x99,
ATA_OP_SLEEPNOW1        = 0xe6,

According to https://sg.danny.cz/sg/sdparm.html
Quote
Table 2. hdparm options that have sdparm equivalent
hdparm option   related sdparm option   Comments
-f              --command=sync          sdparm sends a SYNCHRONIZE CACHE SCSI command; it does not touch the block layer.
-y              --command=stop          places an ATA disk, or a CD/DVD drive, in standby mode

But is this true?

The udisks/sdparm flags should be preferred?
https://t10.org/ftp/t10/document.05/05-344r0.pdf


Step 5.
udisks/udiskslinuxdrive.c:handle_power_off
Code: [Select]
  /* http://git.kernel.org/?p=linux/kernel/git/torvalds/linux.git;a=commit;h=253e05724f9230910344357b1142ad8642ff9f5a */
  remove_path = g_strdup_printf ("%s/remove", g_udev_device_get_sysfs_path (usb_device));
  f = fopen (remove_path, "w");
  ...
      gchar contents[1] = {'1'};
      if (fwrite (contents, 1, 1, f) != 1)

There is no /remove provided for my device.
/delete appears to have the same effect but I do not know how to verify?


Test step 1:

Code: [Select]
$ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
nvme0n1     259:0    0 238.5G  0 disk
├─nvme0n1p1 259:1    0    50M  0 part /boot/efi
├─nvme0n1p2 259:2    0    20G  0 part /
└─nvme0n1p3 259:3    0 218.4G  0 part /home

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 413c:1010 Dell Computer Corp. USB 2.0 Hub [MTT]
Bus 001 Device 005: ID 413c:2110 Dell Computer Corp. Dell Wired Multimedia Keyboard
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub

Connect via USB

Code: [Select]
[2339994.556770] usb 2-1: new SuperSpeed USB device number 10 using xhci_hcd
[2339994.571244] usb 2-1: New USB device found, idVendor=0411, idProduct=0240, bcdDevice= 1.23
[2339994.571254] usb 2-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[2339994.571257] usb 2-1: Product: HD-PCFU3
[2339994.571260] usb 2-1: Manufacturer: BUFFALO
[2339994.571263] usb 2-1: SerialNumber: 00000314000060E5
[2339994.572062] usb-storage 2-1:1.0: USB Mass Storage device detected
[2339994.572384] scsi host2: usb-storage 2-1:1.0
[2339995.581576] scsi 2:0:0:0: Direct-Access     BUFFALO  HD-PCFU3         0000 PQ: 0 ANSI: 3
[2340002.309121] sd 2:0:0:0: [sda] 1953525168 512-byte logical blocks: (1.00 TB/932 GiB)
[2340002.309974] sd 2:0:0:0: [sda] Write Protect is off
[2340002.309978] sd 2:0:0:0: [sda] Mode Sense: 73 00 10 08
[2340002.311189] sd 2:0:0:0: [sda] Write cache: enabled, read cache: enabled, supports DPO and FUA
[2340002.329842]  sda: sda1
[2340002.330025] sd 2:0:0:0: [sda] Attached SCSI disk

$ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    0 931.5G  0 disk
└─sda1        8:1    0 931.5G  0 part
nvme0n1     259:0    0 238.5G  0 disk
├─nvme0n1p1 259:1    0    50M  0 part /boot/efi
├─nvme0n1p2 259:2    0    20G  0 part /
└─nvme0n1p3 259:3    0 218.4G  0 part /home

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 413c:1010 Dell Computer Corp. USB 2.0 Hub [MTT]
Bus 001 Device 005: ID 413c:2110 Dell Computer Corp. Dell Wired Multimedia Keyboard
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 010: ID 0411:0240 BUFFALO INC. (formerly MelCo., Inc.) HD-PCFU3
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub

# lsusb -v -D /dev/bus/usb/002/010
Device: ID 0411:0240 BUFFALO INC. (formerly MelCo., Inc.) HD-PCFU3
Negotiated speed: SuperSpeed (5Gbps)
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               3.00
  bDeviceClass            0 [unknown]
  bDeviceSubClass         0 [unknown]
  bDeviceProtocol         0
  bMaxPacketSize0         9
  idVendor           0x0411 BUFFALO INC. (formerly MelCo., Inc.)
  idProduct          0x0240 HD-PCFU3
  bcdDevice            1.23
  iManufacturer           1 BUFFALO
  iProduct                2 HD-PCFU3
  iSerial                 3 00000314000060E5
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x002c
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              896mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass         8 Mass Storage
      bInterfaceSubClass      6 SCSI
      bInterfaceProtocol     80 Bulk-Only
      iInterface              0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0400  1x 1024 bytes
        bInterval               0
        bMaxBurst              15
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0400  1x 1024 bytes
        bInterval               0
        bMaxBurst              15
Binary Object Store Descriptor:
  bLength                 5
  bDescriptorType        15
  wTotalLength       0x0016
  bNumDeviceCaps          2
  USB 2.0 Extension Device Capability:
    bLength                 7
    bDescriptorType        16
    bDevCapabilityType      2
    bmAttributes   0x00000000
      (Missing must-be-set LPM bit!)
  SuperSpeed USB Device Capability:
    bLength                10
    bDescriptorType        16
    bDevCapabilityType      3
    bmAttributes         0x00
    wSpeedsSupported   0x000e
      Device can operate at Full Speed (12Mbps)
      Device can operate at High Speed (480Mbps)
      Device can operate at SuperSpeed (5Gbps)
    bFunctionalitySupport   1
      Lowest fully-functional device speed is Full Speed (12Mbps)
    bU1DevExitLat          10 micro seconds
    bU2DevExitLat        1000 micro seconds
Device Status:     0x0000
  (Bus Powered)

$ tail -n2 /etc/fstab
UUID=5C74B9B374B98FEE /home/shr/mount/aya ntfs3 rw,noauto,user 0 2

$ mount /dev/sda1

$ tail -n1 /etc/mtab
/dev/sda1 /home/shr/mount/aya ntfs3 rw,nosuid,nodev,noexec,relatime,uid=1000,gid=1000,iocharset=utf8 0 0

$ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    0 931.5G  0 disk
└─sda1        8:1    0 931.5G  0 part /home/shr/mount/aya
nvme0n1     259:0    0 238.5G  0 disk
├─nvme0n1p1 259:1    0    50M  0 part /boot/efi
├─nvme0n1p2 259:2    0    20G  0 part /
└─nvme0n1p3 259:3    0 218.4G  0 part /home

# eject /dev/sda
eject: unable to eject

$ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    0 931.5G  0 disk
└─sda1        8:1    0 931.5G  0 part
nvme0n1     259:0    0 238.5G  0 disk
├─nvme0n1p1 259:1    0    50M  0 part /boot/efi
├─nvme0n1p2 259:2    0    20G  0 part /
└─nvme0n1p3 259:3    0 218.4G  0 part /home

[2342632.956244]  sda: sda1


Test step 2:

Code: [Select]
$ cat /proc/meminfo | grep -ie 'dirty'
Dirty:                267196 kB

$ sync

$ cat /proc/meminfo | grep -ie 'dirty'
Dirty:                20 kB


Test step 3:

Code: [Select]
# sdparm --command=sync /dev/sda
    /dev/sda: BUFFALO   HD-PCFU3          0000

$ cat /proc/meminfo | grep -ie 'dirty'
Dirty:                44 kB


Test step 4:

Code: [Select]
# sdparm --command=stop /dev/sda
    /dev/sda: BUFFALO   HD-PCFU3          0000

Short click sound, no change in device spin sound and LED.

Code: [Select]
[2343000.385143]  sda: sda1

$ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    0 931.5G  0 disk
└─sda1        8:1    0 931.5G  0 part
nvme0n1     259:0    0 238.5G  0 disk
├─nvme0n1p1 259:1    0    50M  0 part /boot/efi
├─nvme0n1p2 259:2    0    20G  0 part /
└─nvme0n1p3 259:3    0 218.4G  0 part /home

# sdparm --command=eject /dev/sda
    /dev/sda: BUFFALO   HD-PCFU3          0000

Did not notice any reaction from sdparm eject.

Code: [Select]
# hdparm -Y /dev/sda
SG_IO: bad/missing sense data, sb[]:  70 00 01 00 00 00 00 0a 00 00 00 00 00 1d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

$ sg_decode_sense 70 00 01 00 00 00 00 0a 00 00 00 00 00 1d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0
0 00 00
Fixed format, current; Sense key: Recovered Error
Additional sense: ATA pass through information available
  error=0x0, status=0x0, device=0x0, count(7:0)=0x0
  extend=0, log_index=0x0, lba_high,mid,low(7:0)=0x0,0x0,0x0

$ man sdparm | grep -e 'recovered error'
       21     the DEVICE reports a "recovered error". The requested command was successful. Most likely a utility  will report a recovered error to stderr and continue, probably leaving the utility with an exit status of 0 .

Device spin sound goes quiet, LED stays on

Code: [Select]
$ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    0 931.5G  0 disk
└─sda1        8:1    0 931.5G  0 part
nvme0n1     259:0    0 238.5G  0 disk
├─nvme0n1p1 259:1    0    50M  0 part /boot/efi
├─nvme0n1p2 259:2    0    20G  0 part /
└─nvme0n1p3 259:3    0 218.4G  0 part /home

# lsusb -v -D /dev/bus/usb/002/010 | tail -n2
Device Status:     0x0000
  (Bus Powered)


Test step 5:

Code: [Select]
# printf '1' > /sys/block/sda/device/delete

[2343533.184469] sd 2:0:0:0: [sda] Synchronizing SCSI cache

Device does otherwise not seem to react at all, LED still on

Code: [Select]
$ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
nvme0n1     259:0    0 238.5G  0 disk
├─nvme0n1p1 259:1    0    50M  0 part /boot/efi
├─nvme0n1p2 259:2    0    20G  0 part /
└─nvme0n1p3 259:3    0 218.4G  0 part /home

Disconnect from USB

Code: [Select]
[2343580.588884] usb 2-1: reset SuperSpeed USB device number 10 using xhci_hcd
[2343581.278968] usb 2-1: USB disconnect, device number 10

Carpe noctem!

Re: Remove USB storage like a caveman

Reply #1
On the command line or in a shell script afaik you can just:
Code: [Select]
# mount /dev/sd? /some/dir
# umount /dev/sd? /some/dir

umount will call sync internally, and sync will block until it completes. The only caveat is that with some older spinning disk drives they might say they have synced when data is in some internal buffer in the drive circuits, but not yet actually written to the magnetic disk, so you might want to add an arbitrary pause to be safe and give time for the hardware to react before you pull out the cable:
Code: [Select]
# mount /dev/sd? /some/dir
# sync
# sleep 10
# umount /dev/sd? /some/dir

I guess you could look at the source code for mount, umount and sync if you want to know more about the low level details of how they work?

Re: Remove USB storage like a caveman

Reply #2
I second #######'s suggestion. This is how I've always done it, and still do. Count to 5 or so before yanking out the stick/cable. For superstitious reasons.

I would also like to voice my opinion that "cavemen" chose to live in caves because that was simple, convenient, and the least-surprise solution in their environment.

Re: Remove USB storage like a caveman

Reply #3
I must be a caveman because I just 'sync' and when completed(back to prompt) I pull the USB stick out.
Or am I missing the point ?

Re: Remove USB storage like a caveman

Reply #4
I must be a caveman because I just 'sync' and when completed(back to prompt) I pull the USB stick out.
Or am I missing the point ?

I think theoretically that is correct, because sometimes when writing large files to disk, the prompt does not appear instantaneously, suggesting that it is waiting for the writing (or reading) to finish. I always wait a few seconds after that before unplugging the disk, but hey what do I know?!  ;D

Re: Remove USB storage like a caveman

Reply #5
If you don't umount then some file systems will record this as an unclean unmount, which can cause problems on some os'es or devices which will then declare the drive to be faulty or claim the contents are corrupt, and you might need to re-format the drive to get rid of the warnings and errors. If you can't be bothered to umount, you can leave the drive in place until you power off because the shut down procedure unmounts all drives automatically.

Re: Remove USB storage like a caveman

Reply #6
The only thing you do, aside from potentially damaging the drive, is confuse the proc information that feeds mount.  Eventually it will clear out the dead device from its table.  The new fangled run/media mechanism should sense the removal but if you mount by hand, like I do, it might be less happy.

sync is your friend, BTW

Re: Remove USB storage like a caveman

Reply #7
Why does hdparm -Y seem to succeed, while the results of sdparm --command=stop seem unexpected in my case? Despite sdparm using the correct(?) ioctl flags?

man hdparm:
Code: [Select]
       [...] USB drive enclosures  now  also support "SAT" (SCSI-ATA Command Translation) and therefore may also work with hdparm. [...]

According to:
https://www.t10.org/ftp/t10/document.04/04-136r0.pdf

... the command translation SYNCHRONIZE CACHE (1) seems to indeed provide a mapping between SCSI 0x35 (sdparm & udisks) and ATA 0xe7 (hdparm).

Recap:
Standby command is hdparm -y.
Sleep command is hdparm -Y.

The command translation START STOP UNIT seems to map SCSI 0x1b (sdparm & udisks) to ATA 0xe0 and 0xe1 (hdparm).

hdparm/sgio.h:
Code: [Select]
	ATA_OP_STANDBYNOW1		= 0xe0,
ATA_OP_IDLEIMMEDIATE = 0xe1,

So hdparm -y should provide the correct flag.

For hdparm -Y, I find no mention of a mapping for the ATA sleep flags. Is this why I get the bad/missing sense data? But it spins the device down anyway and I have no idea why.


According to https://sg.danny.cz/sg/sdparm.html
Quote
Table 2. hdparm options that have sdparm equivalent
hdparm option   related sdparm option   Comments
-f              --command=sync          sdparm sends a SYNCHRONIZE CACHE SCSI command; it does not touch the block layer.
-y              --command=stop          places an ATA disk, or a CD/DVD drive, in standby mode

But is this true?

True, see above.


Code: [Select]
# sdparm --command=sync /dev/sda
    /dev/sda: BUFFALO   HD-PCFU3          0000

$ cat /proc/meminfo | grep -ie 'dirty'
Dirty:                44 kB

I think this does not tell us anything about on-device cache?

There is no /remove provided for my device.
/delete appears to have the same effect but I do not know how to verify?

It seems /sys/.../remove and /sys/.../delete do the same thing and the respective driver just provides a different file name?

man sg_start:
Code: [Select]
       Linux note: best not to use a standard block device name (e.g. /dev/sdc) with the --stop option. Use a sg or bsg device node instead (see lsscsi(8) ). The block layer will sometimes notice the disk spinning down  and  decide: "that's not right" and spin it up again!

This happened sometimes when I tried sdparm.
udisks is using d-bus to identify the device instead? I never worked with d-bus so not sure.

Verify device cache? This only shows the settings but no cache state?
Code: [Select]
# sginfo -c /dev/sda

To summarize:
    1. eject (calls umount which in turn calls sync).
    2. fsync system call. AFAIK sync includes a fsync call?
    3. Use 0x35 to sync the on-device cache. (Also done by fsync since at least 2008?)
    4. Use 0x1b to spin down the device.
    5. Remove the device via sysfs.

New attempt:
    1. # eject /dev/sda
    2. --- done in step 1 (?) ---
    3. # sg_sync /dev/sda  // (from sg3_utils)
    4. # sg_start --stop /dev/bsg/2\:0\:0\:0 // (from sg3_utils)
    5. # printf '1' > /sys/block/sda/device/delete

This now seems to work as expected. Step 3 generates a short 'click' sound on the device. Step 4 spins down.

simple, convenient, and the least-surprise solution in their environment.

Exactly ♡

Re: Remove USB storage like a caveman

Reply #8
Microsoft filesystems (NTFS and FAT) and even some Linux ones like XFS have a dirty bit which records an unclean umount, although this can be cleared it can take a while to find a method that actually works for the case in question. I couldn't really find any single link to explain this in detail and give a list of affected file systems, but anyway, it's been my experience dirty bits can be annoying and I prefer to habitually do an actual umount, while there might be some FS's where it can be potentially skipped.
https://www.partitionwizard.com/clone-disk/dirty-bit.html

If you want to know more about the low level drive operations, this site might be helpful if you have not already found it:
https://wiki.osdev.org/Expanded_Main_Page

 
Artix forum uses a single cookie to remember youOK