[Solved] Get "serial number" of (removable) disk lazarus / 32-Linux ---------------------------------------------------------------------- Bart · 2025-11-15 18:36 Hi, On Windows it's relatively easy to obtain the "Volume Serial" of a disk. I use this for one of my utilities that copies graphical files from SD-cards. I store the serial number with thet last time the program copied the files, so when I ind=sert the same SD-card next time, I can offer the options "copy files newer than ". Since I've move my trusty old laptop to Linux (Mint 22.0 Mate), I would like to achiev the same (or similar) on Linux. For reference here's the code used in Windows: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function GetVolumeInfo(const Volume: String; out VolLabel: String; out SerialID: DWORD;                       out MaxComponentLength, VolumeFlags: DWORD;                       out FileSystemName: String): Boolean;var  VolumeInfo, FileSystemNameBuffer:  array[0..MAX_PATH] of Char;  OldMode: UINT;begin  FillChar(VolumeInfo{%H-}, SizeOf(VolumeInfo), #0);  FillChar(FileSystemNameBuffer{%H-}, SizeOf(VolumeInfo), #0);  MaxComponentLength := 0;  VolumeFlags := 0;  OldMode := SetErrorMode(SEM_FAILCRITICALERRORS);  Result := GetVolumeInformationA(PChar(Volume), @VolumeInfo, SizeOf(VolumeInfo),           @SerialID, MaxComponentLength,           VolumeFlags, PChar(FileSystemNameBuffer), SizeOf(FileSystemNameBuffer));  if Result then  begin    VolLabel := PChar(VolumeInfo);    FileSystemName := PChar(FileSystemNameBuffer);  end;  SetErrorMode(OldMode);end; function GetVolumeSerial(const Volume: String): DWORD;var  MaxComponentLength: DWORD;  VolumeFlags: DWORD;  {%H-}VolumeInfo:  array[0..MAX_PATH] of Char;  {%H-}VolumeSerialNumber: DWORD;  VolLabel, FSN: String;begin  MaxComponentLength := 0;  VolumeFlags := 0;  if not GetVolumeInfo(Volume, VolLabel, Result, MaxComponentLength, VolumeFlags,FSN) then    Result := sidInvalid;end; Bart ---------------------------------------------------------------------- Thaddy · 2025-11-16 07:39 Try ls -l /dev/disk/by-uuid/ Doing it programmatically other than with process redirection is a bit more cumbersome. That requires libblkid-dev and a Pascal translation for -parts of - blkid.h Usually blkid is installed, as is libblkid: it is used by many more utilities. Note that direct device access is restricted to root, but userspace can access the information from the cache. Will translate this example to pascal later:Code: C  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i #include #include  int main() {blkid_cache cache;blkid_dev dev; // Initialize the blkid cacheif (blkid_get_cache(&cache, NULL) < 0) {perror("Failed to initialize cache");return 1;} // Retrieve device informationdev = blkid_get_dev(cache, "/dev/sda1", BLKID_DEV_NORMAL);if (dev) {const char *uuid = blkid_dev_devname(dev);printf("Device UUID: %s\n", uuid);} else {printf("Device not found.\n");} // Free the cacheblkid_put_cache(cache);return 0;} I have also an example that needs root. ---------------------------------------------------------------------- Hartmut · 2025-11-16 08:17 On Linux Ubuntu I use command 'lsblk' for this. I call this command with 'TProcess' and store it's results into a 'TStringList' which I then parse. Command 'lsblk' has plenty of possible parameters. I created a procedure which always uses: lsblk -l -p -e 7 -oNAME,MOUNTPOINT,LABEL,FSTYPE,SIZE,TYPE,PARTTYPE,UUID,MODEL The benefit is, that each value which I ever may need has a fixed column in the result which makes common parsing easier. The result of above command looks like this: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i NAME      MOUNTPOINT            LABEL      FSTYPE   SIZE TYPE PARTTYPE UUID                               MODEL/dev/sda                                        931,5G disk                                               Samsung SSD 860 EVO 1TB/dev/sda1                       Win7       ntfs      55G part 0x7      60D898E99C1DD456/dev/sda2 /media/D              DOS        vfat       2G part 0xe      BE2A-F2D1/dev/sda3                                           1K part 0xf/dev/sda4 /                   Ubuntu     ext4     100G part 0x83     ada05431-c5b7-4241-b1a8-ecbfe2d987cb/dev/sda5                       INSTALL    vfat       2G part 0x6      5B53-12F7/dev/sda6 /media/F            MAILS      vfat       2G part 0x6      A1FF-3DF2/dev/sda7                     WinXP      ntfs       4G part 0x7      6532098E648037CE/dev/sda8 /media/H              BigSize    ntfs   765,5G part 0x7      D09567D4F718A7BA/dev/sda9 /media/X              XCHG       vfat    1023M part 0xb      4749-6ED3/dev/sdb                                          1,8T disk                                             Samsung SSD 870 QVO 2TB/dev/sdb1                       Filme      ntfs     1,7T part 0x7      58F4C81735ED08D0/dev/sdb2                     Ubuntu2    ext4     100G part 0x83     0cdde00b-c715-4d01-8938-b7174f9f574d/dev/sdc                                            0B disk                                             STORAGE DEVICE/dev/sdd                                           14,5G disk                                               OnlyDisk/dev/sdd1 /media/hg6/NETAC      NETAC      vfat    14,5G part 0xc      08BB-83BA/dev/sde                                           59,5G disk                                               STORE N GO/dev/sde1 /media/hg6/STORE N GO STORE N GO vfat    59,5G part 0xc    D3C0-2DE8 You find the "Volume Serial" in column UUID. Please notice, that sometimes this number on Linux is longer than on Windows, but the 8 rightmost digits on Linux are the same than the "Volume Serial" on Windows. ---------------------------------------------------------------------- Thaddy · 2025-11-16 08:25 Here's how to do it in code. Paramstr(1) is the device name , like /dev/sda1 Compile: fpc -k-lblkid blkidexample.pas Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program BlkidExample;{$mode objfpc}{$H+} uses  ctypes; const  libblkid = 'blkid'; type  Pblkid_cache = type POpaqueData; // from system.pas  Pblkid_dev = type POpaqueData;  Pblkid_tag_iterate = type POpaqueData;const  BLKID_DEV_NORMAL = 0; { Function prototypes from blkid.h }function blkid_get_cache(var cache: Pblkid_cache; filename: PChar): cint; cdecl; external libblkid;function blkid_get_dev(cache: Pblkid_cache; devname: PChar; flags: cint): Pblkid_dev; cdecl; external libblkid;function blkid_dev_devname(dev: Pblkid_dev): PChar; cdecl; external libblkid;function blkid_get_tag_value(cache: Pblkid_cache; tagname: PChar; devname: PChar): PChar; cdecl; external libblkid;function blkid_devno_to_devname(devno: culong): PChar; cdecl; external libblkid;procedure blkid_put_cache(cache: Pblkid_cache); cdecl; external libblkid; var  cache: Pblkid_cache;  dev: Pblkid_dev;  devname, uuid, fstype, labelstr: PChar; begin  // Initialize the blkid cache  if blkid_get_cache(cache, nil) < 0 then  begin    WriteLn('Failed to initialize cache');    Halt(1);  end;   // Retrieve device information  dev := blkid_get_dev(cache, PChar(ParamStr(1)), BLKID_DEV_NORMAL);  if dev <> nil then  begin    devname := blkid_dev_devname(dev);    WriteLn('Device name: ', devname);        // Get UUID    uuid := blkid_get_tag_value(cache, 'UUID', devname);  if uuid <> nil then      WriteLn('Device UUID: ', uuid)    else      WriteLn('UUID: not found');        // Get filesystem type    fstype := blkid_get_tag_value(cache, 'TYPE', devname);    if fstype <> nil then      WriteLn('Filesystem: ', fstype)    else      WriteLn('Filesystem: not found');        // Get label    labelstr := blkid_get_tag_value(cache, 'LABEL', devname);    if labelstr <> nil then      WriteLn('Label: ', labelstr)    else      WriteLn('Label: not found');  end  else  begin    WriteLn('Device not found.');  end;   // Free the cache  blkid_put_cache(cache);end.The opaquedata types are something I introduced in system a couple of years ago: The above is a good example of its use. Sample output:Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i $ ./blkidexample /dev/sddDevice name: /dev/sddDevice UUID: 4c4ca96c-e3ef-43d1-b3f5-6b2869757af3Filesystem: ext4Label: not found Tested Ubuntu lts24 and Debian trixie. both 64 bit intel. @Harmut lsblk uses the same library. @Bart The first C code was wrong, this code is based on my development version. Note that not every drive has a UUID. (same as on Windows, btw) ---------------------------------------------------------------------- Bart · 2025-11-16 22:05 Quote from: Hartmut on November 16, 2025, 09:17:41 amThe benefit is, that each value which I ever may need has a fixed column in the result which makes common parsing easier. But, what's up with the SIZE column? It seems that for all other columns, the values start at the same point as where the name of the colums starts, but SIZE seems to be right-justified (both the column nam and it's values)? How then do you know, at what point the values of SIZE start and where they end? Bart ---------------------------------------------------------------------- Aruna · 2025-11-17 03:26 Quote from: Bart on November 16, 2025, 11:05:18 pmQuote from: Hartmut on November 16, 2025, 09:17:41 amThe benefit is, that each value which I ever may need has a fixed column in the result which makes common parsing easier. But, what's up with the SIZE column? It seems that for all other columns, the values start at the same point as where the name of the colums starts, but SIZE seems to be right-justified (both the column nam and it's values)? How then do you know, at what point the values of SIZE start and where they end? Bart Use -P / --pairs option. (Produces KEY="VALUE" format). Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i aruna@debian:~/console$ lsblk -p -o NAME,RM,LABEL,SIZE,UUID,SERIAL,FSTYPE,PTTYPE,MODELNAME        RM LABEL              SIZE UUID                                 SERIAL         FSTYPE PTTYPE MODEL/dev/sda     0                  931.5G                                    S1DGB98C                dos    ST1000DM003-1CH162├─/dev/sda1  0 Ubuntu 12.04.4 L   250G 092d3383-368f-4945-b0c7-b1a3d3ed64df                  ext4   dos    ├─/dev/sda2  0 kernel-stuff       250G 15684a31-dbbf-4411-8fe9-593d79d3a86f                  ext4   dos    ├─/dev/sda3  0 back-up           16.8G b416f822-d40a-44ba-a4ca-82a3c65705e0                  ext4   dos    ├─/dev/sda4  0                      1K                                                            dos    ├─/dev/sda5  0                    204G 3c8fce17-5948-43e7-9662-921e65c7faa7                  ext3   dos    ├─/dev/sda6  0                  196.9G 200f9328-ae6b-4fe9-9d94-24ffd391bbe9                  ext4   dos    ├─/dev/sda7  0                    6.2G 561317be-3304-4d36-ab68-301c6fb8d2ec                  swap   dos    └─/dev/sda8  0                    7.7G eaadb2c8-4967-47e4-90eb-e012cdded9b0                  swap   dos    /dev/sdb     0                  232.9G                                    JP0272J20E462M          dos    Hitachi HDS721025CLA682├─/dev/sdb1  0 System Reserved    100M 72E2E3AAE2E370B1                                      ntfs   dos    ├─/dev/sdb2  0 OS             212.2G 46C81FD6C81FC2DB                                    ntfs   dos    ├─/dev/sdb3  0                      1K                                                            dos    ├─/dev/sdb5  0                 19.7G 460c8aa0-2f69-43ef-887c-0b14e6ecff68                ext4   dos    └─/dev/sdb6  0                    903M 25fda5b3-7fff-45b0-900d-54d07f5afbef                  swap   dos    /dev/sdc     0                  931.5G                                    WD-WCC3F4NA3TFY         dos    WDC WD10EZEX-75M2NA0├─/dev/sdc1  0                  930.6G 94b73f9c-748d-4bd9-876b-3281762b03c6                ext4   dos    ├─/dev/sdc2  0                      1K                                                            dos    └─/dev/sdc5  0                    976M b1d91238-053b-44b0-b329-98d0bf73eb87                  swap   dos    /dev/sdd     1                      0B                                    0000000000000006               Compact Flash/dev/sde     1                    0B                                      0000000000000006               SM/xD-Picture/dev/sdf     1                    0B                                      0000000000000006             SDXC/MMC/dev/sdg     1                      0B                                    0000000000000006               MS/MS- Pro/HG/dev/sdh     1                      0B                                    0000000000000006               SD/MMC/MS/MSPRO/dev/sr0     1                   1024M                                      KK3D9KI3929                    HL-DT-ST DVDRAM GH24NSB0aruna@debian:~/console$    Your friend is: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i lsblk -h -J gives you JSON. Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i aruna@debian:~/console$ lsblk -J /dev/sda -o NAME,SIZE,LABEL,TYPE,MODEL{   "blockdevices": [      {         "name": "sda",         "size": "931.5G",         "label": null,         "type": "disk",         "model": "ST1000DM003-1CH162",         "children": [            {               "name": "sda1",             "size": "250G",               "label": "Ubuntu 12.04.4 L",             "type": "part",               "model": null            },{             "name": "sda2",               "size": "250G",               "label": "kernel-stuff",               "type": "part",               "model": null            },{               "name": "sda3",             "size": "16.8G",               "label": "back-up",               "type": "part",               "model": null            },{             "name": "sda4",               "size": "1K",               "label": null,               "type": "part",               "model": null          },{               "name": "sda5",               "size": "204G",             "label": null,               "type": "part",               "model": null            },{               "name": "sda6",             "size": "196.9G",               "label": null,               "type": "part",               "model": null            },{             "name": "sda7",               "size": "6.2G",               "label": null,               "type": "part",               "model": null            },{               "name": "sda8",               "size": "7.7G",               "label": null,               "type": "part",               "model": null            }         ]      }   ]} ---------------------------------------------------------------------- Hartmut · 2025-11-17 07:48 Quote from: Bart on November 16, 2025, 11:05:18 pmQuote from: Hartmut on November 16, 2025, 09:17:41 amThe benefit is, that each value which I ever may need has a fixed column in the result which makes common parsing easier. But, what's up with the SIZE column? It seems that for all other columns, the values start at the same point as where the name of the colums starts, but SIZE seems to be right-justified (both the column nam and it's values)? How then do you know, at what point the values of SIZE start and where they end? Bart I'm sorry, I gave you the wrong parameters (picked up a line which I had only used for testing purposes). I recommend to use: lsblk -l -e 7 -oNAME,FSTYPE,UUID,PARTTYPE,TYPE,MOUNTPOINT The result of above command looks like this: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i NAME FSTYPE UUID                                 PARTTYPE TYPE MOUNTPOINTsda                                                     disksda1 ntfs   60D898E99C1DD456                     0x7      partsda2 vfat   BE2A-F2D1                            0xe      part /media/Dsda3                                           0xf      partsda4 ext4   ada05431-c5b7-4241-b1a8-ecbfe2d987cb 0x83     part /sda5 vfat   5B53-12F7                            0x6      partsda6 vfat   A1FF-3DF2                            0x6      part /media/Fsda7 ntfs 6532098E648037CE                     0x7      partsda8 ntfs   D09567D4F718A7BA                     0x7      part /media/Hsda9 vfat 4749-6ED3                            0xb      part /media/Xsdb                                                     disksdb1 ntfs   58F4C81735ED08D0                     0x7      partsdb2 ext4   0cdde00b-c715-4d01-8938-b7174f9f574d 0x83     partsdc                                                     disksdd                                                     disksdd1 vfat   08BB-83BA                          0xc      part /media/hg6/NETACsde                                                     disksde1 vfat   D3C0-2DE8                          0xc      part /media/hg6/STORE N GO I placed colum MOUNTPOINT as the last colum, because it is the only one which can contain blanks. Some parsing hints:  - split each line into "words" (separated by blanks)  - skip all lines which have less than 5 "words" (they have no UUID)  - if you want to find the requested line by NAME it should be easy  - if you want to find the requested line by MOUNTPOINT and if this can contain blanks in your case, then you must determine the start of the 6th "word" and use everything from this position on. Or search for the 1st "/" and use everything from this position on. If you need more help you are welcome. ---------------------------------------------------------------------- Bart · 2025-11-17 10:18 My solution would have been to drop SIZE as well. I think the order does not matter that much, since (at least it looks that way) all fields have fixed sizes, which can be determined by examining the header (first line). For the last field you can simply use Copy(Line, StartPos, MaxInt). Thanks for the help so far. I'm not in a rush, just getting familiar with this kind of stuff. Bart ---------------------------------------------------------------------- Aruna · 2025-11-20 00:51 Quote from: Bart on November 15, 2025, 07:36:59 pmHi, On Windows it's relatively easy to obtain the "Volume Serial" of a disk. I use this for one of my utilities that copies graphical files from SD-cards. I store the serial number with thet last time the program copied the files, so when I ind=sert the same SD-card next time, I can offer the options "copy files newer than ". Since I've move my trusty old laptop to Linux (Mint 22.0 Mate), I would like to achiev the same (or similar) on Linux. Bart Hi, the attached console program does the job. Simple, lean, and blazing fast! ---------------------------------------------------------------------- Bart · 2025-11-20 17:49 Quote from: Aruna on November 20, 2025, 01:51:15 amHi, the attached console program does the job. Simple, lean, and blazing fast! Thanks for the example. In the mean time I've written my own parser for the lsblk output. Maybe not very fast, but since this is about disk IO, it'll be orders of magnitude faste than the actual runnig of lsblk. In the long run I'm plannig to make a TBlockDeviceComboBox usable on Windows and Linux, where I can fill it's contents depending onm e.g. disk type and wether or not "disk in drive". Bart ---------------------------------------------------------------------- PascalDragon · 2025-11-20 20:48 Quote from: Bart on November 15, 2025, 07:36:59 pmOn Windows it's relatively easy to obtain the "Volume Serial" of a disk. Please note that this is the “Volume ID” or “File system ID” of the volume or file system, not of the disk. If the disk is partitioned with MBR or GPT it will have its own “Disk ID”. And the disk itself will also have one or more hardware serials. E.g. if you have a ATA disk attached to a USB adapter you will have the USB serial, the SCSI VPD serial and the ATA serial or if you have a NVMe you'll have the controller serial, the namespace EUI and the namespace GUID. So while you clarified that you need this for detecting the same file system (for which the volume ID is fine) the title is a bit misleading in that regard. ---------------------------------------------------------------------- Bart · 2025-11-20 22:30 Yes, I found that out by now. But, for my needs, what I've got so far (SerialID that GetVolumeInfo, which is the same as UUID that lsblk gives) is what I need. It identifies this particular disk with the current filesystem on it (well: this filesystem on this particular disk). Ir's the contenst of the disc I'm interested in. If someone formats the disk or changes the filesystem, it'll be  a "new" disk for all intends and purposes (in the context of my program). It might not be fail safe, but for my simple needs it will do. Bart ---------------------------------------------------------------------- Aruna · 2025-11-21 02:44 Quote from: Bart on November 20, 2025, 06:49:45 pmQuote from: Aruna on November 20, 2025, 01:51:15 amHi, the attached console program does the job. Simple, lean, and blazing fast! Thanks for the example. Your most welcome. Quote from: Bart on November 20, 2025, 06:49:45 pmIn the mean time I've written my own parser for the lsblk output. Maybe not very fast, but since this is about disk IO, it'll be orders of magnitude faste than the actual runnig of lsblk. In the long run I'm plannig to make a TBlockDeviceComboBox usable on Windows and Linux, where I can fill it's contents depending onm e.g. disk type and wether or not "disk in drive". Bart I would love to see your parser once finished if we may? I did something similar a while back and all I am saying is if your going with a Tcombo or TStringGrid may all the gods grant you patience and strength. It is doable but why go bald trying when a Tmemo gets the job done and very well with a mono space font  ;)  I have attached a zip which hopefully will get you started. The screenshots speak for themselevs. ---------------------------------------------------------------------- d2010 · 2025-11-21 06:43 Quote from: Bart on November 20, 2025, 06:49:45 pm Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i //nb: GetDiskFree() is used internally only when creating//multi-disk archives on floppies so I've chosen to ignore free space > 2gigfunction GetDiskFree(const Filename: string): cardinal;var   SectorsPCluster, BytesPSector, FreeClusters, TotalClusters: DWORD;   Drive: string;begin   Result   := 0;   if (length(Filename) < 3) or (pos(':\',Filename) <> 2) then exit;   Drive := copy(Filename,1,3);   if GetDiskFreeSpace( pChar( Drive ),     SectorsPCluster, BytesPSector, FreeClusters, TotalClusters ) then        result := FreeClusters * SectorsPCluster * BytesPSector;end;//------------------ --------------------------------------------------- function GetVolumeName(DrivePath: string; out VolumeName: string): boolean;var  OldErrMode: dword;  VolName: array[0..255] of char; //nb: can be > 11 chars with NTFS drives  dummy1: {$IFDEF VER120_PLUS}Cardinal{$ELSE}Integer{$ENDIF};  dummy2: dword;begin  DrivePath := AppendSlash(DrivePath);  OldErrMode := SetErrorMode( SEM_FAILCRITICALERRORS );  result := GetVolumeInformation(pchar(DrivePath),               VolName, sizeof(VolName), nil, dummy1, dummy2, nil, 0 );  if result then VolumeName := VolName;  SetErrorMode( OldErrMode );end; ---------------------------------------------------------------------- Thaddy · 2025-11-21 09:22 That is Windows.... Did you read the original question? I answered that. As did some others by deferring to TProcess.. I gave the code. ---------------------------------------------------------------------- Bart · 2025-11-21 14:34 Quote from: Aruna on November 21, 2025, 03:44:42 amQuote from: Bart on November 20, 2025, 06:49:45 pmIn the mean time I've written my own parser for the lsblk output. Maybe not very fast, but since this is about disk IO, it'll be orders of magnitude faste than the actual runnig of lsblk. In the long run I'm plannig to make a TBlockDeviceComboBox usable on Windows and Linux, where I can fill it's contents depending onm e.g. disk type and wether or not "disk in drive". I would love to see your parser once finished if we may? Goto https://svn.code.sf.net/p/flyingsheep/code/trunk/MijnLib/ and check out fsidiskutils.pp and fsiunixutils.pp. Implementation may very wel change in the next days though. And everything about he parsing is hard-coded. Quote from: Aruna on November 21, 2025, 03:44:42 am... and all I am saying is if your going with a Tcombo or TStringGrid may all the gods grant you patience and strength. It is doable but why go bald trying when a Tmemo gets the job done and very well with a mono space font  I have attached a zip which hopefully will get you started. The screenshots speak for themselevs. Because the end goal is to have a dropdown list where the user can select the drive (Windows) or mountpoint (Linux), without having to use a TSelectDirectory dialog or something similar. The appication I need it for resides in https://svn.code.sf.net/p/flyingsheep/code/trunk/dccopy/. Just take a look at the interface (the main form): it's supposed to be usable by users that don't even knwo how to start a filemanager... And, since it's simple and remembers the last time you used the inserted disk, I use it myself quite often. The program dates back to the time that plugging a digital camera into your system requiered software by the manufacturer of the camera, and more often than not, this software ceased to function after some time (it would not recognize the camera) or had too much bloat to make it usable for me. Bart ---------------------------------------------------------------------- Aruna · 2025-11-21 21:47 Quote from: Bart on November 21, 2025, 03:34:35 pmQuote from: Aruna on November 21, 2025, 03:44:42 amI would love to see your parser once finished if we may? Goto https://svn.code.sf.net/p/flyingsheep/code/trunk/MijnLib/ and check out fsidiskutils.pp and fsiunixutils.pp. Implementation may very wel change in the next days though. And everything about he parsing is hard-coded. Thank you. Quote from: Aruna on November 21, 2025, 03:44:42 am... and all I am saying is if your going with a Tcombo or TStringGrid may all the gods grant you patience and strength. It is doable but why go bald trying when a Tmemo gets the job done and very well with a mono space font  ;)  I have attached a zip which hopefully will get you started. The screenshots speak for themselevs. Quote from: Bart on November 21, 2025, 03:34:35 pmBecause the end goal is to have a dropdown list where the user can select the drive (Windows) or mountpoint (Linux), without having to use a TSelectDirectory dialog or something similar. The appication I need it for resides in https://svn.code.sf.net/p/flyingsheep/code/trunk/dccopy/. Just take a look at the interface (the main form): it's supposed to be usable by users that don't even knwo how to start a filemanager... And, since it's simple and remembers the last time you used the inserted disk, I use it myself quite often. The program dates back to the time that plugging a digital camera into your system requiered software by the manufacturer of the camera, and more often than not, this software ceased to function after some time (it would not recognize the camera) or had too much bloat to make it usable for me. Bart Hey, your runniing OS check worked like magic as can be seen in the attached screenshot. I also like your UI I think it's neat! Just wish I understood dutch. Give me some time to go through your code base and see if I can give you something useful and simple. You will probbaly find a fix way before that .... ;D ---------------------------------------------------------------------- Bart · 2025-11-21 23:43 Quote from: Bart on November 21, 2025, 03:34:35 pmBecause the end goal is to have a dropdown list where the user can select the drive (Windows) or mountpoint (Linux), without having to use a TSelectDirectory dialog or something similar. Started implementing this (TBlockDeviceComboBox ) in FsiDeviceComboBox. Once that works to my staisfaction, I'll start using that control in my dccopy program, so I'll be able to use it on my linyx laptop as well. (My new Win11 laptop does not hav a cardreader, my old Win10 (now Linux Mint 22.0 MATE) does have one, so running this on my linux machine is of personal interest to me. I'm too lazy to buy a cardreader for my new laptop ...) [EDIT] Updated link to FsiDeviceComboBox unit (renamed the unit). [/EDIT] Bart ---------------------------------------------------------------------- Aruna · 2025-11-22 11:20 Quote from: Bart on November 22, 2025, 12:43:12 amQuote from: Bart on November 21, 2025, 03:34:35 pmBecause the end goal is to have a dropdown list where the user can select the drive (Windows) or mountpoint (Linux), without having to use a TSelectDirectory dialog or something similar. Started implementing this (TBlockDeviceComboBox ) in FsiControls. Once that works to my staisfaction, I'll start using that control in my dccopy program, so I'll be able to use it on my linyx laptop as well. (My new Win11 laptop does not hav a cardreader, my old Win10 (now Linux Mint 22.0 MATE) does have one, so running this on my linux machine is of personal interest to me. I'm too lazy to buy a cardreader for my new laptop ...) Bart Bart, is there a specific reason you have to go with lsblk? I was experimenting last night and realized I can get a TShellTreeView and a TShellListView working with zero lines of custom code, a lot simpler than all the if checks with lsblk. I’m not 100% sure, but I think this approach should work cross-platform on Linux, Windows, and macOS. Basically, you just set the Root property to the directory (or logical device) you want the tree view to populate. On my system, when I plug in my USB, it mounts as /media/aruna/EMTEC 450, so I set the root to /media/aruna and everything works perfectly. I’ve attached a zip, give it a try. It could save you a lot of headaches down the line. Anyway, I’m planning to develop this further for my own toolbox. :) ---------------------------------------------------------------------- paweld · 2025-11-22 13:05 @Bart - the easiest way is to parse the data in JSON format. Below is the modified code for the fsiunixutils.pp unit: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i { FSIUnixUtils   Copyright (C) 2025 by Bart Broersma and Flying Sheep Inc.   This library is free software; you can redistribute it and/or modify it  under the terms of the GNU Library General Public License as published by  the Free Software Foundation; either version 2 of the License, or (at your  option) any later version with the following modification:   As a special exception, the copyright holders of this library give you  permission to link this library with independent modules to produce an  executable, regardless of the license terms of these independent modules,and  to copy and distribute the resulting executable under terms of your choice,  provided that you also meet, for each linked independent module, the terms  and conditions of the license of that module. An independent module is a  module which is not derived from or based on this library. If you modify  this library, you may extend this exception to your version of the library,  but you are not obligated to do so. If you do not wish to do so, delete this  exception statement from your 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 General Public License  for more details.   You should have received a copy of the GNU Library General Public License  along with this library; if not, write to the Free Software Foundation,  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.} Unit FsiUnixUtils; interface uses  SysUtils, Classes, Process, FsiDiskUtils, fpjson; type   TDriveType = FsiDiskUtils.TDriveType;  TDriveTypes = FsiDiskUtils.TDriveTypes;  const  NonFixedDrives = FsiDiskUtils.NonFixedDrives;  AllDriveTypes = FsiDiskUtils.AllDriveTypes;  DriveTypeStrings: array[TDriveType] of String = ('dtUnknown',    'dtNoRootDir',    'dtRemovable',    'dtFixed',    'dtRemote',    'dtCdrom',    'dtRamDisk',    'dtDisk',  'dtPart',    'dtDeviceMapper',    'dtMultipleDevices',    'dtLoop'  );  function GetBlockDeviceInfoRecs(Refresh: Boolean): TBlockDeviceInfoRecs;function GetRemovableMountedDisks(Refresh: Boolean): TBlockDeviceInfoRecs; implementation type  TBlockDeviceInfoList = class(TStringList); var  CachedBlockDeviceInfoRecs: TBlockDeviceInfoRecs = nil;  CachedBlockDeviceInfoRecsInitialized: Boolean = False; procedure ParseBlockDeviceInfoList(Info: TBlockDeviceInfoList; out BlockDeviceInfoRecs: TBlockDeviceInfoRecs);var  i: Integer;  jd: TJsonData;  jarr: TJsonArray;  jobj: TJsonObject;//const//  BoolStr: Array[Boolean] of String = ('FALSE','TRUE');begin  BlockDeviceInfoRecs := [];  if (Info.Count = 0) then    Exit;  try    jd := GetJson(Info.Text);    if (jd.FindPath('blockdevices') <> nil) and (jd.FindPath('blockdevices').JSONType = jtArray) then    begin      jarr := TJsonArray(jd.FindPath('blockdevices'));      SetLength(BlockDeviceInfoRecs, jarr.Count);      for i := 0 to jarr.Count - 1 do      begin        jobj := jarr.Objects[i];        BlockDeviceInfoRecs[i].NAME := jobj.FindPath('name').AsString;        //writeln(format('BlkInfoRecs[%d].NAME=%s',[i,BlockDeviceInfoRecs[i].N AME]));         if not jobj.FindPath('mountpoint').IsNull then        BlockDeviceInfoRecs[i].MountPoint := jobj.FindPath('mountpoint').AsString;        //writeln(format('BlkInfo Recs[%d].MountPoint=%s',[i,BlockDeviceInfoRecs[i].MOUNTPOINT]));       if not jobj.FindPath('label').IsNull then          BlockDeviceInfoRecs[i].&LABEL := jobj.FindPath('label').AsString;      //writeln(format('BlkInfoRecs[%d].&LABEL=%s',[i,BlockDeviceInfoRecs[ i].&LABEL]));         if not jobj.FindPath('fstype').IsNull then        BlockDeviceInfoRecs[i].FSTYPE := jobj.FindPath('fstype').AsString;        //writeln(format('BlkInfoRecs[%d].FSTYPE=%s',[i,BlockDeviceInfoR ecs[i].FSTYPE]));         if not jobj.FindPath('type').IsNull then        BlockDeviceInfoRecs[i].&TYPE := jobj.FindPath('type').AsString;      //writeln(format('BlkInfoRecs[%d].&TYPE=%s',[i,BlockDeviceInfoRec s[i].&TYPE]));         if not jobj.FindPath('parttype').IsNull then        BlockDeviceInfoRecs[i].PartTYPE := jobj.FindPath('parttype').AsString;        //writeln(format('BlkInfoRe cs[%d].PARTTYPE=%s',[i,BlockDeviceInfoRecs[i].PARTTYPE]));         if not jobj.FindPath('uuid').IsNull then          BlockDeviceInfoRecs[i].UUID := jobj.FindPath('uuid').AsString;        //writeln(format('BlkInfoRecs[%d].UUID=%s',[i,BlockDeviceInfoRecs[i].U UID]));         if not jobj.GetPath('model').IsNull then          BlockDeviceInfoRecs[i].MODEL := jobj.FindPath('model').AsString;      //writeln(format('BlkInfoRecs[%d].MODEL=%s',[i,BlockDeviceInfoRecs[i] .MODEL]));         if not jobj.FindPath('tran').IsNull then          BlockDeviceInfoRecs[i].TRAN := jobj.FindPath('tran').AsString;        //writeln(format('BlkInfoRecs[%d].TRAN=%s',[i,BlockDeviceInfoRecs[i].T RAN]));         BlockDeviceInfoRecs[i].RM := jobj.FindPath('rm').AsBoolean;        //writeln(format('BlkInfoRecs[%d ].RM=%s',[i,BoolStr[BlockDeviceInfoRecs[i].RM]]));         BlockDeviceInfoRecs[i].SIZE := jobj.FindPath('size').AsInt64;        / /writeln(format('BlkInfoRecs[%d].SIZE=%d',[i,BlockDeviceInfoRecs[i].SI ZE]));         case BlockDeviceInfoRecs[i].&TYPE of          'part' : BlockDeviceInfoRecs[i].DeviceType := dtpart;          'disk' : BlockDeviceInfoRecs[i].DeviceType := dtDisk;          'rom' : BlockDeviceInfoRecs[i].DeviceType := dtCdRom;          'loop' : BlockDeviceInfoRecs[i].DeviceType := dtLoop;          'lvm', 'crypt', 'dmraid', 'mpath', 'path', 'dm' : BlockDeviceInfoRecs[i].DeviceType := dtDeviceMapper;          'md', 'linear', 'raid0', 'raid1', 'raid4', 'raid5', 'raid10', 'multipath' : BlockDeviceInfoRecs[i].DeviceType := dtMultipleDevices;          otherwise BlockDeviceInfoRecs[i].DeviceType := dtUnknown;        end;//case       //writeln;      end;    end;  finally    jd.Free;  end;end;  procedure GetBlockDeviceInfoList(BlockDeviceInfoList: TBlockDeviceInfoList);var  Proc: TProcess;  i: Integer;begin  BlockDeviceInfoList.Clear;  Proc := TProcess.Create(nil);  //lsblk -l -e 7 -A -o NAME,MOUNTPOINT,LABEL,FSTYPE,TYPE,PARTTYPE,UUID,MODEL,TRAN,RM,SIZE  //lsblk -l -a -o NAME,MOUNTPOINT,LABEL,FSTYPE,TYPE,PARTTYPE,UUID,MODEL,TRAN,RM,SIZE   try  Proc.Executable := 'lsblk';  //ToDo: get this from path  ??  Proc.Parameters.Add('-l'); //list  Proc.Parameters.Add('-J'); //as json  Proc.Parameters.Add('-b'); //size in bytes  //Proc.Parameters.Add('-e7'); //exclude RAM drives  Proc.Parameters.Add('-a');  Proc.Parameters.Add('-o');  Proc.Parameter s.Add('NAME,MOUNTPOINT,LABEL,FSTYPE,TYPE,PARTTYPE,UUID,MODEL,TRAN,RM,S IZE');  Proc.Options := Proc.Options + [poWaitOnExit, poUsePipes];  Proc.Execute;  BlockDeviceInfoList.LoadFromStream(Proc.Output);  for i := BlockDeviceInfoList.Count - 1 downto 0 do    if (Trim(BlockDeviceInfoList[i])='') then      BlockDeviceInfoList.Delete(i);  finally    Proc.Free;  end;end;  function GetBlockDeviceInfoRecs(Refresh: Boolean): TBlockDeviceInfoRecs;var  BlockDeviceInfoList: TBlockDeviceInfoList;begin  if (not Refresh) and CachedBlockDeviceInfoRecsInitialized then    Exit(CachedBlockDeviceInfoRecs);  Result := [];  BlockDeviceInfoList := TBlockDeviceInfoList.Create;  try    GetBlockDeviceInfoList(BlockDeviceInfoList);    ParseBlockDeviceInfoList(BlockDeviceInfoList,  CachedBlockDeviceInfoRecs);    CachedBlockDeviceInfoRecsInitialized := True;    Result := CachedBlockDeviceInfoRecs;  finally    BlockDeviceInfoList.Free;  end;end; function GetRemovableMountedDisks(Refresh: Boolean): TBlockDeviceInfoRecs;var  i: Integer;begin  if Refresh then    GetBlockDeviceInfoRecs(True);  Result := [];  for i := 0 to Length(CachedBlockDeviceInfoRecs) - 1 do  begin    if (CachedBlockDeviceInfoRecs[i].RM) and (CachedBlockDeviceInfoRecs[i].MOUNTPOINT <> '') then    begin      SetLength(Result, Length(Result) + 1);      Result[Length(Result) - 1] := CachedBlockDeviceInfoRecs[i];    end;  end;end; initialization finalization  CachedBlockDeviceInfoRecs := nil;end.  Changes in the ParseBlockDeviceInfoList and GetBlockDeviceInfoList (here I only added new parameters) procedures. ---------------------------------------------------------------------- Bart · 2025-11-22 18:11 @paweld: thanks for the example. I chose a "light weight" solution for parsing. Cumbersome, but at least I can understand the code an where/why it goes wrong (if it does). Didn't work with JSON a lot, so did not consider it at all. I included your code (as comment) in the unit for reference. Might use it in the (near) future. Bart ---------------------------------------------------------------------- Bart · 2025-11-29 17:09 Quote from: Aruna on November 22, 2025, 12:20:56 pmBart, is there a specific reason you have to go with lsblk? I was experimenting last night and realized I can get a TShellTreeView and a TShellListView working with zero lines of custom code, a lot simpler than all the if checks with lsblk. AFAIK then TShellTreeView/TShellListView have no notice of: wether or not a drive (Windows) or folder represents a removable mounted device like an US stick or an SDCardthe UUID of the disk (well, actually UUID of the filesystem on that disk) Both of the above is what I need. Bart