ASMFS & dbms_diskgroup.read

Urh SrecnikUrh Srecnik
5 min 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

  • v$asm_file

  • v$asm_alias

  • How to Dump or Extract a Raw Block From a File Stored in an ASM Diskgroup (Doc ID 603962.1)

0
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.