ASMFS & dbms_diskgroup.read


Here's what I've learned while trying to implement an ASMFS (which is an open-source GitHub project) and where my implementation fell short of my initial expectations.
Initial Expectations
I can access v$asm_file
and v$asm_alias
, which provide a complete directory structure in a documented way. So, I can render those as a real filesystem, and I can treat aliases as symlinks.
But can I read the contents of those files? Well, according to a quick Google search, apparently, I can do that also, using the dbms_diskgroup.read()
call.
So, I thought, awesome, let's go! The idea needed a bit of fiddling with how to map each file/symlink to a specific inode number and how to resolve a specific inode number back to the actual v$asm_file
. But the official Oracle documentation is solid, so this part worked as envisioned.
However, about a weekend later, I discovered a few limitations of this initial idea.
The Results
Since a picture is said to be worth a thousand words, here’s a screenshot. But do read on, as there are limits to what it can do.
Limitations of dbms_diskgroup.read
While accessing performance views and rendering the directory structure worked more or less flawlessly, reading the files using dbms_diskgroup.read
comes with a few nuances I did not expect.
Before describing them, please note that this is an undocumented procedure. So, everything I describe here could be wrong or could be different in different versions of Oracle ASM. What I'm describing comes from my trial and error, so ymmv.
Here's the procedure signature:
begin dbms_diskgroup.read(:b_handle, :b_offset, :b_length, :b_buffer); end;
Parameter :b_handle
In order to obtain :b_handle
and other needed parameters, you need to call this:
dbms_diskgroup.getfileattr(:b_target, :b_filetype, :b_filesize, :b_blksize);
dbms_diskgroup.open(:b_target, :b_mode, :b_filetype, :b_blksize, :b_handle, :b_pblksize, :b_filesize);
And when you finish, you need to call:
dbms_diskgroup.close(:b_handle);
Parameters :b_offset
and :b_length
My trial and error show that :b_offset
is expected to be given in the number of blocks, while :b_length
is expected to be given in the number of bytes.
Also, the :b_length
must be a multiple of block_size
(e.g., a multiple of 8192 for 8k datafiles and 512 bytes for archive logs, etc. - you get the expected block size from the previously mentioned dbms_diskgroup.getfileattr
).
:b_offset
seems to be an IN
parameter, and :b_length
seems to be an IN OUT
parameter, which makes sense: IN is the number of bytes expected to be read, OUT is the number of bytes actually read.
Parameter :b_buffer
This seems to be OUT RAW(32767)
and holds raw data returned by the read call.
Notice how Oracle's max size for the RAW
datatype is one byte less than 32K. That means reading datafiles with a 32K block size is likely not possible using this procedure.
Block zero (file headers)
Does :b_offset
start with 0 or with 1 to return the first block? :)
Well, I tried reading a block with :b_offset=0
, and here are the results.
On 19.7, it reports an error if we try to read one block at offset 0 for datafiles and archivelogs.
On 19.27 and 19.28, it returns data.
So, on versions where data is returned, what is returned?
I tried copying the file using asmcmd cp
and rman backup as copy
. Those two produced identical results. But what dbms_diskgroup.read
returned was a bit different. The first block was a bit different, and the file size was slightly off. All other blocks (:b_offset>=1
) were identical.
At first, I thought I made some kind of coding error, which may still be the case, but so far, I think the reason for this lies elsewhere.
Here's the hexdump of block zero of a random archive log, as copied using asmfs/dbms_read
:
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 00 22 00 00 00 00 c0 ff ┊ 00 00 00 00 00 00 00 04 │⋄"⋄⋄⋄⋄××┊⋄⋄⋄⋄⋄⋄⋄•│
│00000010│ 55 27 00 00 00 02 00 00 ┊ 91 fa 02 00 7d 7c 7b 7a │U'⋄⋄⋄•⋄⋄┊×ו⋄}|{z│
│00000020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│
│* │ ┊ │ ┊ │
│00000200│ ┊ │ ┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
And here's the one of the same file using asmcmd cp
:
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 00 22 00 00 00 00 c0 ff ┊ 00 00 00 00 00 00 00 04 │⋄"⋄⋄⋄⋄××┊⋄⋄⋄⋄⋄⋄⋄•│
│00000010│ f5 a6 00 00 00 02 00 00 ┊ 91 fa 02 00 7d 7c 7b 7a │××⋄⋄⋄•⋄⋄┊×ו⋄}|{z│
│00000020│ a0 81 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │××⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│
│00000030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│
│* │ ┊ │ ┊ │
│00000200│ ┊ │ ┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
Notice how they're a bit different? It seems to me, that headers in ASM might be stored a bit differently than on filesystem.
File Size
The following are copies of the same archivelog; 100.arch.1
was obtained using asmfs/dbms_diskgroup.read
, and the other one, 100.arch.2
, was obtained using asmcmd cp
.
$ ls -l /tmp/100.arch.*
-rwxr-xr-x. 1 oracle oinstall 99951104 Aug 15 13:43 /tmp/100.arch.1
-rw-r-----. 1 oracle oinstall 99951616 Aug 15 13:43 /tmp/100.arch.2
Notice the size in bytes, a 512-byte difference. Let's compare this to what v$asm_file
says:
select a.file_number, f.bytes, f.blocks, f.block_size, f.blocks*f.block_size as bb_check
from v$asm_alias a
join v$asm_file f on f.file_number = a.file_number
where a.name='thread_1_seq_100.288.1207485831';
FILE_NUMBER BYTES BLOCKS BLOCK_SIZE BB_CHECK
----------- ---------- ---------- ---------- ----------
288 99951616 195218 512 99951616
All OK, but, look at what dbms_diskgroup.getfileattr
(which provides arguments for open() and read() calls) returns:
set serveroutput on;
declare
b_target varchar2(500) := '+DATA/DBSE/ARCHIVELOG/2025_07_26/thread_1_seq_100.288.1207485831';
b_filetype number;
b_filesize number;
b_blksize number;
begin
dbms_diskgroup.getfileattr(b_target, b_filetype, b_filesize, b_blksize);
dbms_output.put_line('file_type=' || b_filetype);
dbms_output.put_line('file_size=' || b_filesize);
dbms_output.put_line('blksize=' || b_blksize);
end;
/
file_type=4
file_size=195217
blksize=512
It's one block less than what v$asm_file
reports! That's why asmfs files are one block shorter than those copied using asmcmd cp
.
Final Thoughts
If anyone has a hint on how to "persuade" dbms_diskgroup.read()
to return header blocks as they should be on a regular file system, then this filesystem could become much more than it currently is.
Consider how awesome it would be to access complete ASM diskgroups from a remote server as if the files were on a local filesystem. Well, I look forward to technical discussions at the next *OUG events. Also, feel free to reach out to me with ideas.
Resources
How to Dump or Extract a Raw Block From a File Stored in an ASM Diskgroup (Doc ID 603962.1)
Subscribe to my newsletter
Read articles from Urh Srecnik directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Urh Srecnik
Urh Srecnik
I'm an Oracle DBA and a developer at Abakus Plus d.o.o.. My team is responsible for pro-active maintenance of many Oracle Databases and their infrastructure. I am co-author of Abakus's solutions for monitoring Oracle Database performance, APPM, also available as a Free Edition. for backup and recovery: Backup Server for quick provisioning of test & development databases: Deja Vu Also author of open-source DDLFS filesystem which is available on GitHub. I am: OCP Database Administrator OCP Java SE Programmer OCIS Exadata Database Machine and a few more, check my LinkedIn profile for the complete list.