# ASMFS & dbms_diskgroup.read

Here's what I've learned while trying to implement an [ASMFS](https://github.com/usrecnik/asmfs) (which is an open-source [GitHub project](https://github.com/usrecnik/asmfs)) 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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1755285450866/8c0c560a-ec91-4730-80a9-8ab849c8951d.png align="center")

## 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:

```plaintext
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:

```plaintext
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:

```plaintext
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`:

```plaintext
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│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`:

```plaintext
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│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`.

```plaintext
$ 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:

```plaintext
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:

```plaintext
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;
/
```

```plaintext
file_type=4
file_size=195217
blksize=512
```

It's one block less than what `v$asm_file` reports! That's why [asmfs](https://github.com/usrecnik/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](https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/V-ASM_FILE.html)
    
* [v$asm\_alias](https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/V-ASM_ALIAS.html)
    
* How to Dump or Extract a Raw Block From a File Stored in an ASM Diskgroup (Doc ID 603962.1)
