netatalk.io

Dev Docs Virtual Icon

Virtual Icon — Custom Volume Icons Over AFP

Synthesizing Icon\r resource forks in memory so Classic Mac OS clients display custom volume icons without any physical file on the host filesystem. This feature was introduced in Netatalk 4.5.0.

Overview

When a classic Macintosh connects to an AFP file share, the Finder looks for a specially named file at the root of each volume called Icon\r (the four letters “Icon” followed by a carriage return character, 0x0D). If this file exists and the volume root directory has the hasCustomIcon bit set in its Finder info, the Finder uses the icon resources inside the file to display a custom volume icon instead of the generic shared folder.

Netatalk synthesizes a virtual Icon\r file entirely in memory. No file is written to the host filesystem. The server intercepts the relevant AFP operations and serves the icon data on the fly.

This feature is disabled by default and can be enabled on a per-volume basis with the legacy icon option in afp.conf.

Source Files

File Purpose
etc/afpd/virtual_icon.c Resource fork construction, public API
etc/afpd/virtual_icon.h Constants (VIRTUAL_ICON_NAME, VIRTUAL_ICON_CNID), function prototypes
etc/afpd/icon.c Icon pixel data arrays (ICN#, icl4, icl8 for each icon)
etc/afpd/icon.h Icon size constants, resource type codes, extern declarations
etc/afpd/fork.c materialize_virtual_icon(), virtual fork handling in afp_openfork(), afp_read(), afp_write()
etc/afpd/fork.h AFPFORK_VIRTUAL flag, struct ofork with virtual data fields
etc/afpd/ofork.c Virtual fork close handling in of_closefork()
etc/afpd/filedir.c Virtual icon interception in FPGetFilDirParms, FPDelete, FPRename, FPMoveAndRename, FPSetFilDirParms
etc/afpd/enumerate.c Appending virtual Icon\r entry during enumeration
etc/afpd/directory.c Setting FINDERINFO_HASCUSTOMICON on volume root, incrementing offspring count
etc/afpd/volume.c Calling virtual_icon_init() during volume mount
etc/afpd/status.c Server status block with ICN# icon (independent of virtual icon feature)

Implementation

Initialization

When a volume is mounted, virtual_icon_init() in etc/afpd/virtual_icon.c checks whether the legacy icon option is set. If so, it selects the matching icon data arrays from etc/afpd/icon.c and calls build_resource_fork() to construct the complete resource fork binary in a heap-allocated buffer stored in vol->v_icon_rfork.

%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart TD
    A["afp.conf parsed<br/><i>netatalk_conf.c</i>"]:::yellow
    A -->|"legacy icon = globe"| B["vol→v_legacyicon = 'globe'"]:::yellow
    B --> C["Volume mount<br/><i>volume.c</i>"]:::blue
    C --> D["virtual_icon_init(vol)<br/><i>virtual_icon.c</i>"]:::blue
    D --> E{"v_legacyicon set?"}:::grey
    E -->|No| F["Feature disabled<br/>v_icon_rfork = NULL"]:::grey
    E -->|Yes| G["Select icon data arrays<br/>(ICN#, icl4, icl8)<br/><i>icon.h / icon.c</i>"]:::purple
    G --> H["build_resource_fork()<br/><i>virtual_icon.c</i>"]:::purple
    H --> I["Allocate buffer<br/>256 + data_len + map_len bytes"]:::cyan
    I --> J["Write resource header<br/>offsets and lengths"]:::orange
    J --> K["Write data section<br/>ICN# 256B + icl4 512B + icl8 1024B"]:::orange
    K --> L["Write resource map<br/>type list + reference list"]:::orange
    L --> M["vol→v_icon_rfork = buffer<br/>vol→v_icon_rfork_len = size"]:::green

    classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
    classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
    classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
    classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
    classDef grey fill:#dfe6e9,stroke:#333,rx:10,ry:10
    classDef cyan fill:#81ecec,stroke:#333,rx:10,ry:10
    classDef orange fill:#ff9f43,stroke:#333,rx:10,ry:10

Available Icon Themes

The virtual_icon_init() function maps the legacy icon config value to icon data arrays. Each icon provides three formats (ICN#, icl4, icl8):

Config Value ICN# Array icl4 Array icl8 Array
daemon daemon_icon daemon_icon_icl4 daemon_icon_icl8
declogo declogo_icon declogo_icon_icl4 declogo_icon_icl8
fileserver fileserver_icon fileserver_icon_icl4 fileserver_icon_icl8
globe globe_icon globe_icon_icl4 globe_icon_icl8
hagar hagar_icon hagar_icon_icl4 hagar_icon_icl8
nas nas_icon nas_icon_icl4 nas_icon_icl8
sdcard sdcard_icon sdcard_icon_icl4 sdcard_icon_icl8
sunlogo sunlogo_icon sunlogo_icon_icl4 sunlogo_icon_icl8

All arrays are declared in etc/afpd/icon.h and defined in etc/afpd/icon.c.

Key Constants

Defined in etc/afpd/virtual_icon.h and etc/afpd/icon.h:

Constant Value Description
VIRTUAL_ICON_NAME "Icon\x0D" The Mac filename: “Icon” + carriage return
VIRTUAL_ICON_CNID 16 Reserved CNID below CNID_START (17)
ICN_HASH_SIZE 256 ICN# resource size (icon + mask)
ICL4_SIZE 512 icl4 resource size (32×32 4-bit)
ICL8_SIZE 1024 icl8 resource size (32×32 8-bit)
RESTYPE_ICNH 0x49434E23 Resource type code for ‘ICN#’
RESTYPE_ICL4 0x69636C34 Resource type code for ‘icl4’
RESTYPE_ICL8 0x69636C38 Resource type code for ‘icl8’
ICON_RES_ID −16455 Finder volume custom icon resource ID (0xBFB9)
RFORK_HEADER_SIZE 16 Size of resource fork header (4 uint32 fields)

Public API Functions

Defined in etc/afpd/virtual_icon.c:

Function Description
virtual_icon_init() Initialize the virtual icon for a volume; builds the resource fork buffer
virtual_icon_enabled() Returns true if vol->v_icon_rfork is non-NULL
real_icon_exists() Returns true if a physical Icon\r file exists at the volume root (via lstat())
is_virtual_icon_name() Returns true if the name matches VIRTUAL_ICON_NAME
virtual_icon_get_rfork() Returns a pointer to the pre-built resource fork buffer and its length
virtual_icon_getfilparams() Synthesizes AFP file parameters for the virtual file, matching the requested bitmap

Icon Data

Icon Resource Formats

Each icon is 32×32 pixels. The three resource types store different color depths:

Resource Bits/pixel Size (bytes) Format
ICN# 1 256 128 bytes icon bitmap + 128 bytes mask, both 32×32 packed 1-bit, MSB first, 4 bytes per row
icl4 4 512 32×32 pixels, 4 bits each (2 pixels per byte, high nibble first), indexed into the standard Mac 4-bit CLUT
icl8 8 1024 32×32 pixels, 8 bits each (1 pixel per byte), indexed into the standard Mac 8-bit CLUT

All formats are raw pixel data with no header — the dimensions are implied by the resource type.

Resource Fork Binary Layout

The build_resource_fork() function in etc/afpd/virtual_icon.c constructs the classic Mac resource fork format:

%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart LR
    subgraph "Resource Header — 256 bytes (offset 0)"
        RH["data_offset=256 | map_offset=256+data_len<br/>data_len=1804 | map_len=90<br/>+ 240 bytes reserved (zero)"]:::yellow
    end
    subgraph "Resource Data — 1804 bytes (offset 256)"
        RD["[4B len=256] ICN# 256B<br/>[4B len=512] icl4 512B<br/>[4B len=1024] icl8 1024B"]:::blue
    end
    subgraph "Resource Map — 90 bytes (offset 2060)"
        RM["Header copy (28B) | Type list: 3 types (26B)<br/>Reference list: 3 entries, ID −16455 (36B)"]:::purple
    end
    RH --> RD --> RM

    classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
    classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
    classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10

Resource header (256 bytes at offset 0): - Bytes 0–3: data_offset = 256 (offset to resource data) - Bytes 4–7: map_offset = 256 + data_len (offset to resource map) - Bytes 8–11: data_len = 1804 (total data section length) - Bytes 12–15: map_len = 90 (total map section length) - Bytes 16–255: reserved (zero-filled)

Resource data section (1804 bytes at offset 256): - Each resource is prefixed by a 4-byte big-endian length, followed by the raw data - ICN#: 4 + 256 = 260 bytes - icl4: 4 + 512 = 516 bytes - icl8: 4 + 1024 = 1028 bytes

Resource map (90 bytes at offset 2060): - 22 bytes: copy of header fields + reserved handle + file ref - 2 bytes: attributes (0) - 2 bytes: type list offset (28, from map start) - 2 bytes: name list offset (90, past all entries — no names used) - Type list: 2 bytes (num_types − 1 = 2) + 3 × 8 bytes (type code + count + ref offset) - Reference list: 3 × 12 bytes (resource ID + name offset 0xFFFF + attrs + data offset + handle)

All three resources use resource ID −16455 (0xBFB9), which is the Finder’s volume custom icon resource ID.

Standard Macintosh Color Lookup Tables

The 4-bit and 8-bit icons use indexed color with fixed system CLUTs:

4-bit CLUT (16 colors):

Index Color
0x00 White
0x01 Yellow
0x02 Orange
0x03 Red
0x04 Magenta
0x05 Purple
0x06 Blue
0x07 Cyan
0x08 Green
0x09 Dark Green
0x0A Brown
0x0B Tan
0x0C Light Gray
0x0D Medium Gray
0x0E Dark Gray
0x0F Black

8-bit CLUT (256 colors): The standard Macintosh 8-bit system palette, arranged in a 6×6×6 color cube (indices 0–214) plus a grayscale ramp and system colors. Key entries: 0x00 = White, 0xFF = Black, 0xD4 = medium blue. A full table can be found in Inside Macintosh: Imaging With QuickDraw, Plate 2.


Integration Points

AFP Operation Interception

The virtual icon hooks into multiple AFP command paths. Each check follows the same guard pattern: the request targets the volume root (DIRDID_ROOT), the name matches VIRTUAL_ICON_NAME, the feature is enabled (virtual_icon_enabled(vol)), and no real Icon\r file exists (!real_icon_exists(vol)).

%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart TD
    REQ["AFP Request from Client"]:::grey
    REQ --> CMD{"AFP Command?"}:::grey

    CMD -->|FPEnumerate| ENUM["enumerate.c"]:::blue
    ENUM --> ENUMCHK{"At volume root?<br/>Icon enabled?<br/>No real Icon\\r?<br/>Past last real entry?"}:::grey
    ENUMCHK -->|Yes| ENUMADD["Append virtual Icon\\r entry<br/>via virtual_icon_getfilparams()<br/>Increment offspring count"]:::green
    ENUMCHK -->|No| ENUMREAL["Normal enumeration only"]:::grey

    CMD -->|FPGetFilDirParms| GFDP["filedir.c"]:::blue
    GFDP --> GFDPCHK{"CNID = VIRTUAL_ICON_CNID<br/>or name = Icon\\r<br/>at volume root?"}:::grey
    GFDPCHK -->|Yes| GFDPVIRT["Return synthesized params<br/>virtual_icon_getfilparams()"]:::green
    GFDPCHK -->|No| GFDPREAL["Normal file/dir params"]:::grey

    CMD -->|FPOpenFork| OPEN["fork.c"]:::blue
    OPEN --> OPENCHK{"Name = Icon\\r?<br/>At volume root?<br/>Icon enabled?"}:::grey
    OPENCHK -->|No| OPENREAL["Normal open"]:::grey
    OPENCHK -->|Yes| OPENWR{"Write access<br/>requested?"}:::grey
    OPENWR -->|Yes| MATERIALIZE["materialize_virtual_icon()<br/>Create real file, then normal open"]:::salmon
    OPENWR -->|No| OPENVIRT["Allocate virtual ofork<br/>flags |= AFPFORK_VIRTUAL<br/>Store rfork pointer"]:::green

    CMD -->|FPRead| READ["fork.c"]:::blue
    READ --> READCHK{"AFPFORK_VIRTUAL<br/>flag set?"}:::grey
    READCHK -->|No| READREAL["Normal read from disk"]:::grey
    READCHK -->|Yes| READFORK{"Which fork?"}:::grey
    READFORK -->|Data fork| READEOF["Return AFPERR_EOF<br/>(empty data fork)"]:::yellow
    READFORK -->|Resource fork| READBUF["Serve from in-memory buffer<br/>via dsi_readinit()"]:::green

    CMD -->|FPCloseFork| CLOSE["ofork.c"]:::blue
    CLOSE --> CLOSECHK{"AFPFORK_VIRTUAL?"}:::grey
    CLOSECHK -->|No| CLOSEREAL["Normal close<br/>ad_close / flush"]:::grey
    CLOSECHK -->|Yes| CLOSEVIRT["of_dealloc(ofork)<br/>Skip flush/close"]:::green

    CMD -->|"FPWrite /<br/>FPSetForkParms"| REJECT1["fork.c"]:::blue
    REJECT1 --> REJCHK1{"AFPFORK_VIRTUAL?"}:::grey
    REJCHK1 -->|Yes| MATWR{"Write access?"}:::grey
    MATWR -->|Yes| MATERIALIZE2["materialize_virtual_icon()"]:::salmon
    MATWR -->|No| OLOCK1["Return AFPERR_OLOCK"]:::red
    REJCHK1 -->|No| NORMAL1["Normal write path"]:::grey

    CMD -->|"FPDelete / FPRename /<br/>FPMoveAndRename /<br/>FPSetFilDirParms"| REJECT2["filedir.c"]:::blue
    REJECT2 --> REJCHK2{"Icon\\r at root?<br/>Icon enabled?<br/>No real Icon\\r?"}:::grey
    REJCHK2 -->|Yes| OLOCK2["Return AFPERR_OLOCK"]:::red
    REJCHK2 -->|No| NORMAL2["Normal operation"]:::grey

    classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
    classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
    classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
    classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
    classDef grey fill:#dfe6e9,stroke:#333,rx:10,ry:10
    classDef salmon fill:#fab1a0,stroke:#333,rx:10,ry:10
    classDef red fill:#ee5a5a,stroke:#333,rx:10,ry:10

Interception Summary

AFP Command Source File Virtual Behavior
FPEnumerate etc/afpd/enumerate.c Appends one extra entry for Icon\r after normal directory enumeration at volume root; offspring count incremented by 1
FPGetFilDirParms etc/afpd/filedir.c When cname() returns ENOENT for Icon\r at volume root, or CNID matches VIRTUAL_ICON_CNID (16), returns synthesized file parameters via virtual_icon_getfilparams()
FPOpenFork etc/afpd/fork.c Read-only: allocates a virtual ofork with AFPFORK_VIRTUAL flag, pointing to the pre-built buffer. Write access: calls materialize_virtual_icon()
FPRead etc/afpd/fork.c Virtual forks serve data from of_virtual_data via dsi_readinit(). Data fork reads return AFPERR_EOF immediately
FPCloseFork etc/afpd/ofork.c Virtual forks skip ad_close/flush and simply call of_dealloc()
FPWrite / FPSetForkParms etc/afpd/fork.c With write access: calls materialize_virtual_icon(). Without: returns AFPERR_OLOCK
FPDelete / FPRename / FPMoveAndRename / FPSetFilDirParms etc/afpd/filedir.c Returns AFPERR_OLOCK when targeting Icon\r at volume root
FPGetSrvrInfo etc/afpd/status.c Unchanged — the ICN# icon continues to be embedded in the server status block via status_icon() independently of the virtual icon feature

Volume Root Directory

The getdirparams() function in etc/afpd/directory.c modifies two things when the virtual icon is active:

  1. Finder Info flags: Sets FINDERINFO_HASCUSTOMICON (0x0400) in the Finder info flags for DIRDID_ROOT. This tells the Finder to look for the Icon\r file.

  2. Offspring count: Increments the directory offspring count by 1 to account for the virtual Icon\r entry.

Both modifications are conditional on: dir->d_did == DIRDID_ROOT && virtual_icon_enabled(vol) && !real_icon_exists(vol).

Virtual CNID

The virtual file uses CNID 16 (VIRTUAL_ICON_CNID), which is below CNID_START (17) and above the special directory IDs (DIRDID_ROOT_PARENT = 1, DIRDID_ROOT = 2). CNIDs 3–16 are unassigned and never used by the CNID database, making 16 safe from collisions.

File Attributes

The virtual Icon\r file, as synthesized by virtual_icon_getfilparams(), reports:

Attribute Value Description
Finder flags ATTRBIT_INVISIBLE Hidden from normal directory listings
Finder info flags FINDERINFO_INVISIBLE Set at FINDERINFO_FRFLAGOFF
Data fork length 0 Empty data fork
Resource fork length ~2150 bytes The pre-built resource fork buffer
Unix permissions S_IFREG \| 0444 Read-only regular file
Parent DID DIRDID_ROOT (2) Always at volume root
CNID 16 VIRTUAL_ICON_CNID

The ofork Structure

The struct ofork in etc/afpd/fork.h includes virtual icon support fields:

Field Type Purpose
of_flags int Bitfield; AFPFORK_VIRTUAL (1 << 8) indicates a virtual fork
of_virtual_data const unsigned char * Pointer to the pre-built resource fork buffer
of_virtual_len off_t Length of the virtual data

The AFPFORK_VIRTUAL flag (defined at value 1 << 8 in etc/afpd/fork.h) is checked throughout the fork operation paths to bypass normal file I/O.


Virtual Fork Lifecycle

%%{ init: { 'themeVariables': { 'fontSize': '14px' } } }%%
sequenceDiagram
    participant Client as Mac Finder
    participant AFP as afpd
    participant Vol as Volume (in-memory)

    Note over Vol: virtual_icon_init()<br/>builds rfork buffer

    Client->>AFP: FPEnumerate (volume root)
    AFP->>AFP: Normal enumeration
    AFP->>AFP: Append virtual Icon\r entry<br/>offspring++
    AFP-->>Client: Directory listing with Icon\r

    Client->>AFP: FPGetFilDirParms("Icon\r")
    AFP->>AFP: cname() → ENOENT
    AFP->>AFP: virtual_icon_getfilparams()<br/>CNID=16, invisible, rfork len
    AFP-->>Client: Synthesized file params

    Client->>AFP: FPOpenFork("Icon\r", read, resource)
    AFP->>AFP: Allocate ofork<br/>flags = AFPFORK_VIRTUAL | ACCRD
    AFP->>Vol: Store pointer to v_icon_rfork
    AFP-->>Client: Fork refnum

    Client->>AFP: FPRead(refnum, offset=0, count=2150)
    AFP->>Vol: Read from of_virtual_data
    AFP-->>Client: Resource fork bytes

    Client->>AFP: FPCloseFork(refnum)
    AFP->>AFP: of_dealloc(ofork)<br/>Skip ad_close/flush
    AFP-->>Client: OK

    Note over Client: Finder renders<br/>custom volume icon

Materialization

When a client opens the virtual Icon\r file with write access, the server materializes it into a real file on disk. This is handled by materialize_virtual_icon() in etc/afpd/fork.c.

Materialization Flow

%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart TD
    A["FPOpenFork with write access<br/><i>fork.c</i>"]:::blue
    A --> B["Create real 'Icon\\r' file on disk<br/>open(path, O_CREAT | O_WRONLY)"]:::orange
    B --> C["Seed resource fork<br/>ad_write(ADEID_RFORK) with virtual data"]:::orange
    C --> D["Set FinderInfo<br/>FINDERINFO_INVISIBLE"]:::purple
    D --> E["ad_flush() + ad_close()"]:::purple
    E --> F["Re-init adouble for real I/O<br/>ad_open(path, ADFLAGS_SETSHRMD | ADFLAGS_RDWR)"]:::blue
    F --> G["Stat new file<br/>Update ofork key (dev, inode)"]:::cyan
    G --> H["Clear AFPFORK_VIRTUAL flag<br/>of_virtual_data = NULL"]:::green
    H --> I["real_icon_exists() → true<br/>All subsequent AFP ops<br/>use normal file paths"]:::green

    classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
    classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
    classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
    classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
    classDef grey fill:#dfe6e9,stroke:#333,rx:10,ry:10
    classDef cyan fill:#81ecec,stroke:#333,rx:10,ry:10
    classDef orange fill:#ff9f43,stroke:#333,rx:10,ry:10

Materialization Steps

  1. Create file: open() creates the physical Icon\r at the volume root
  2. Seed resource fork: ad_write() copies the virtual icon data into the real file’s resource fork via adouble
  3. Set FinderInfo: Writes FINDERINFO_INVISIBLE flag to the adouble metadata
  4. Flush and close: ad_flush() + ad_close() ensures all metadata is written
  5. Re-open for real I/O: Reinitializes the adouble structure and re-opens the fork with ADFLAGS_SETSHRMD | ADFLAGS_RDWR
  6. Update ofork: Stats the new file to update dev/inode in the ofork key
  7. Transition: Clears AFPFORK_VIRTUAL, sets of_virtual_data = NULL
  8. Bypass disabled: Since real_icon_exists() now returns true, all subsequent AFP operations use normal code paths

Volume Root Finder Info

%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart LR
    A["FPGetFilDirParms<br/>for volume root<br/><i>directory.c</i>"]:::blue
    A --> B{"d_did == DIRDID_ROOT<br/>AND virtual_icon_enabled(vol)<br/>AND !real_icon_exists(vol)?"}:::grey
    B -->|Yes| C["Read existing Finder flags"]:::purple
    C --> D["flags |= FINDERINFO_HASCUSTOMICON<br/>(0x0400)"]:::green
    D --> E["Write modified flags<br/>back to response"]:::green
    D --> F["Increment offspring count"]:::green
    B -->|No| G["Return unmodified<br/>Finder info"]:::grey
    E --> H["Finder sees hasCustomIcon bit<br/>→ looks for Icon\\r file"]:::yellow

    classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
    classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
    classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
    classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
    classDef grey fill:#dfe6e9,stroke:#333,rx:10,ry:10

Configuration

Enabling Virtual Icons

The legacy icon option in afp.conf enables the feature on a per-volume basis:

[MyVolume]
path = /path/to/volume
legacy icon = globe

Set the value to one of the available icon themes: daemon, declogo, fileserver, globe, hagar, nas, sdcard, sunlogo.

To disable (default), either omit the option or set it to an empty string.

Custom Icons

To use a custom icon instead of the built-in themes:

  1. Create a 32×32 pixel icon in the three required formats (ICN#, icl4, icl8)
  2. Add the icon data arrays to etc/afpd/icon.c
  3. Declare the arrays in etc/afpd/icon.h
  4. Add the new theme name to the if/else chain in virtual_icon_init() in etc/afpd/virtual_icon.c

The contrib/scripts/icn_hex_to_c.pl Perl script in contrib/scripts/ can assist with converting hex icon data to C array format.

Converting From Modern Formats

To convert a 32×32 PNG to the Mac icon formats:

  1. ICN#: Threshold the image to 1-bit (black/white), pack into 128 bytes. Generate the mask similarly (typically the icon silhouette). Concatenate icon + mask = 256 bytes.

  2. icl4: For each pixel, find the nearest color in the 4-bit CLUT. Pack two pixels per byte (high nibble = left pixel). Total: 512 bytes.

  3. icl8: For each pixel, find the nearest color in the 8-bit CLUT. One pixel per byte. Total: 1024 bytes.

Interaction With Real Icons

If a real Icon\r file exists on disk at the volume root, the virtual icon feature is automatically bypassed. All checks include the !real_icon_exists(vol) guard, which calls lstat() on the path vol->v_path/Icon\r.

Once materialized (via write-open), the real_icon_exists() check returns true, and the virtual interception is permanently disabled for that volume (until the real file is deleted).

Footnotes

This is a mirror of the Netatalk GitHub Wiki

Last updated 2026-04-25