Rockbox.org home
release
dev builds
extras
themes manual
wiki
device status forums
mailing lists
IRC bugs
patches
dev guide



Rockbox mail archive

Subject: Windows sym-link code (standalone) for you to test
From: rockbox_at_diffenbach.org
Date: 2003-02-01


There was a request on this list to have rockbox process Microsoft Windows
sym-links (*.lnk) files. A poster posted a link to "The Windows Shortcut
File Format as reverse-engineered by Jesse Hager jessehager_at_iname.com",

Perusing this document, I've come up with the following code (see below).
This code isn't integrated into Rockbox; it's a standalone test (see below
for why). A few notes are in order. The .lnk file contains the absolute path
to a resource, and some data about what that object is. It may or may not
contain a relative path.

My .lnk files differ from those described by Mr. Hager, in that the relative
path is interspersed with nuls ((char) 0). I don't know if this is an
oversight on his part, or a peculiarity of my system (Windows 2000 Service
Pack 3). For this reason, I'd like to ask any other windows users to compile
the code below and test it against some population of their .lnk files.

The relative path is important, I think, because the way I get my mp3s onto
my Archos is to utility called TreeComp. TreeComp compares files from two
directory trees, and copies missing files from one to the other. TreeComp
does not "fix" .lnk files to "correct" the absolute path. (Nor would I want
it to; that wouldn't be a bit for bit copy.) As I suspect others may use
similar utilities, I think it's preferable to find the .lnk's relative path
entry, rather than the absolute path.

What I'm most interested in is if you get a correct relative path, and what
fraction of your sym-links that point to a file or directory do not have a
relative path.

I'm also interested in having my code critiqued for compliance with
"best-practice" Rockbox code; it's been a while since I've coded any
straight C, or for small memory devices (contrary to a previous post, I have
coded for small memory devices, if the Palm OS counts). I'm especially
interested in what you might think of my substitute for C++ exceptions/RAII
with the read_lnk/do_read_lnk nested call construct.

Note that I haven't divided the code into a header and a .c file, for your
immediate convenience. This code compiles under gcc (GCC) 3.2 20020927
(prerelease), and runs under Windows 2000 SP3/cygwin.

Thanks,
--Tom

Please find the code below
// lnk.c

// begin header
#define LNK_VERBOSE 1

typedef long dword ;
typedef dword qword[ 2 ] ;

#define GUID_LENGTH 16
// const int GUID_LENGTH = 16

typedef unsigned char GUID[ GUID_LENGTH ] ;

struct lnk_header {
        dword indicator ;
        GUID guid ;
        dword flags ;
        dword file_attr ;
        qword time1 ;
        qword time2 ;
        qword time3 ;
        dword file_length ;
        dword icon_number ;
        dword showWnd ;
        dword hot_key ;
        dword unknown1 ;
        dword unknown2 ;
} ;

struct file_location_info {
        dword total_length ;
        dword first_following_offset ;
        dword flags ;
        dword local_volume_info_offset ;
        dword base_pathname_offset ;
        dword network_volume_info_offset ;
        dword remaining_pathname_offset ;
} ;

struct lnk_header_and_next_length {
        struct lnk_header header ;
        unsigned short next_length ;
} ;

#define lnk_header_indicator 0x4C

GUID lnk_GUID_1 = {
        0x01, 0x14, 0x02, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0xC0, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x46
} ;

//The flags

#define HAS_SHELL_ITEM_LIST 0x01
#define POINTS_TO_FILE 0x02
#define HAS_DESCRIPTION_STRING 0x04
#define HAS_RELATIVE_PATH 0x08
#define HAS_WORKING_DIRECTORY 0x10
#define HAS_COMMAND_LINE_ARGS 0x20
#define HAS_CUSTOM_ICON 0x40

#ifdef LNK_VERBOSE
static /*const*/ char* flag_message[ 7][ 2 ] = {
        { "The shell item id list is absent", "The shell item id list is
present" },
        { "Points to something else", "Points to a file or directory" },
        { "No description string", "Has a description string" },
        { "No relative path", "Has a relative path string" },
        { "No working directory", "Has a working directory" },
        { "No command line arguments", "Has command line arguments" },
        { "Has the default icon", "Has a custom icon" },
} ;
#endif

#define TARGET_READ_ONLY 0x01
#define TARGET_HIDDEN 0x02
#define TARGET_SYSTEM 0x04
#define TARGET_VOLUME_LABEL 0x08
#define TARGET_DIRECTORY 0x10
#define TARGET_ARCHIVE 0x20
#define TARGET_ENCRYPTED 0x40
#define TARGET_NORMAL 0x80
#define TARGET_TEMPORARY 0x100
#define TARGET_SPARSE 0x200
#define TARGET_REPARSE 0x400
#define TARGET_COMPRESSED 0x800
#define TARGET_OFFLINE 0x1000

#define VOLUME_LOCAL 0x1
#define VOLUME_NETWORK 0x2

#ifdef LNK_VERBOSE
static /*const*/ char* target_message[] = {
        "Target is read only.",
        "Target is hidden.",
        "Target is a system file.",
        "Target is a volume label. (Not possible)",
        "Target is a directory.",
        "Target has been modified since last backup. (archive)",
        "Target is encrypted (NTFS EFS)",
        "Target is Normal??",
        "Target is temporary.",
        "Target is a sparse file.",
        "Target has reparse point data.",
        "Target is compressed.",
        "Target is offline."
} ;
#endif

#define LNK_OK_RELATIVE_PATH 3
#define LNK_OK_ABSOLUTE_PATH 2
#define LNK_OK_NOT_A_FILE_OR_DIRECTORY 1
#define LNK_NOT_A_LNK_FILE 0
#define LNK_BAD_GUID -1
#define LNK_PATH_BUFFER_TOO_SHORT -2
#define LNK_SEEK_ERROR -3
#define LNK_READ_ERROR -4

#ifdef LNK_VERBOSE
#define LNK_ERROR_ADJUST 4
char* lnk_err_message[] = {
    "LNK_READ_ERROR",
    "LNK_SEEK_ERROR",
    "LNK_PATH_BUFFER_TOO_SHORT",
    "LNK_BAD_GUID",
    "LNK_NOT_A_LNK_FILE",
    "LNK_OK_NOT_A_FILE_OR_DIRECTORY",
    "LNK_OK_ABSOLUTE_PATH",
    "LNK_OK_RELATIVE_PATH"
} ;
#endif

// end header

#include <fcntl.h>
#include<io.h>
#include <stdio.h>
#include <string.h>

int read_lnk( int file_handle, char* path_buffer, unsigned int*
path_buffer_length ) {
    int ret ;
    ret = do_read_lnk( file_handle, path_buffer, path_buffer_length ) ;
    close( file_handle ) ;
    return ret ;
}

int do_read_lnk( int file_handle, char* path_buffer, unsigned int*
path_buffer_length ) {

        int ret ;
        struct lnk_header_and_next_length header ;
        int GUID_BAD = 0 ;
        int i ;
        long m ;
        off_t file_offset ;
        long read_len ;
    long read_len_2 ;
    char* path_ptr = path_buffer ;
    unsigned short string_len ;
    dword base_path_offset ;
    char buf2[ 256 ] ;

        if( ( ret = read( file_handle, &header, sizeof( header ) ) ) != sizeof(
header ) )
                return LNK_READ_ERROR ;

        if( header.header.indicator != lnk_header_indicator )
                return LNK_NOT_A_LNK_FILE ;

        if( memcmp( (void*) header.header.guid, (void*) lnk_GUID_1, sizeof(
GUID ) ) ) {
                return LNK_BAD_GUID ;
        }

        for( i = 0, m = HAS_SHELL_ITEM_LIST ; m <= HAS_CUSTOM_ICON ; m <<= 1,
++i ) {
                printf( "%s\n", flag_message[ i ][ ( header.header.flags & m ) == m ] ) ;
        }

        for( i = 0, m = TARGET_READ_ONLY ; m <= TARGET_OFFLINE ; m <<= 1, ++i ) {
                if( ( header.header.file_attr & m ) == m ) {
                        printf( "%s\n", target_message[ i ] ) ;
                }
        }

    if( ! ( header.header.flags & POINTS_TO_FILE ) )
        return LNK_OK_NOT_A_FILE_OR_DIRECTORY ;

        file_offset = ( header.header.flags & HAS_SHELL_ITEM_LIST ?
header.next_length : 0 ) - sizeof( unsigned short ) ;
        // we'll skip the ITEMIDLIST if it's present

        if( lseek( file_handle, file_offset, SEEK_CUR ) == -1 )
                return LNK_SEEK_ERROR ;

        struct file_location_info loc_info ;
        if( ( ret = read( file_handle, &loc_info, sizeof( loc_info ) ) ) !=
sizeof( loc_info ) )
                return LNK_READ_ERROR ;

        printf( "Total length %u, next %u, flags %u, vol offset %u, base offset %u,
network offset %u, remaining %u\n",
                loc_info.total_length ,
                loc_info.first_following_offset ,
                loc_info.flags ,
                loc_info.local_volume_info_offset ,
                loc_info.base_pathname_offset ,
                loc_info.network_volume_info_offset ,
                loc_info.remaining_pathname_offset
        ) ;

    if( loc_info.flags & VOLUME_LOCAL ) {
        base_path_offset = loc_info.base_pathname_offset ;
    } else {
        base_path_offset = loc_info.network_volume_info_offset + 0x14 ;
    }

        if( ( read_len = loc_info.remaining_pathname_offset - base_path_offset )
        + ( read_len_2 = loc_info.total_length -
loc_info.remaining_pathname_offset ) > *path_buffer_length )
                return LNK_PATH_BUFFER_TOO_SHORT ;

        file_offset = base_path_offset - sizeof( loc_info ) ;

        if( (ret = lseek( file_handle, file_offset, SEEK_CUR ) ) == -1 )
                return LNK_SEEK_ERROR ;

        if( ( ret = read( file_handle, path_buffer, read_len ) ) != read_len )
                return LNK_READ_ERROR ;

    path_ptr = path_buffer + ret ;

    // position path_ptr to point to first (char)0 in path_buffer
    while( ( ! *( path_ptr - 1 ) ) && path_ptr > path_buffer ) {
        --path_ptr ;
    }

    // read remaining path
    file_offset = loc_info.remaining_pathname_offset - base_path_offset -
ret ;

    if( file_offset ) {
        if( (ret = lseek( file_handle, file_offset, SEEK_CUR ) ) == -1 ) {
            return LNK_SEEK_ERROR ;
        }
    }

    if( ( ret = read( file_handle, path_ptr, read_len_2 ) ) != read_len_2 )
                return LNK_READ_ERROR ;

        // this should already be \0, unless we got a pathologically short buffer,
but set it anyway.
        *( path_ptr += ret ) = 0 ;

       // position path_ptr to point to first (char)0 in path_buffer
    while( ( ! *( path_ptr - 1 ) ) && path_ptr > path_buffer ) {
        --path_ptr ;
    }

    *path_buffer_length = path_ptr - path_buffer ;
    printf( "absolute path, length %d: <%s>\n", *path_buffer_length,
path_buffer ) ;

    // skip the description string, if any
    if( header.header.flags & HAS_DESCRIPTION_STRING ) {
        if( read( file_handle, &string_len, sizeof( unsigned short ) ) !=
sizeof( unsigned short ) )
            return LNK_READ_ERROR ;
        printf( "description string length = %d\n", string_len ) ;
        if( ( ret = lseek( file_handle, string_len + string_len,
SEEK_CUR ) ) == -1 )
            return LNK_SEEK_ERROR ;
    }

    // read relative path, if any
    if( header.header.flags & HAS_RELATIVE_PATH ) {
        if( ( ret = read( file_handle, &string_len, sizeof( unsigned
short ) ) ) != sizeof( unsigned short ) )
           return LNK_READ_ERROR ;

        read_len = string_len + string_len ;
        if( ( ret = read( file_handle, path_buffer, read_len ) ) !=
read_len )
            return LNK_READ_ERROR ;

        // copy over the nuls
        for( i = 1 ; i < string_len ; ++i ) {
            path_buffer[ i ] = path_buffer[ i * 2 ] ;
        }
        path_buffer[ i ] = 0 ;
        *path_buffer_length = string_len ;
        printf( "relative path, length %d: <%s>\n", *path_buffer_length,
path_buffer ) ;

        return LNK_OK_RELATIVE_PATH ;
    }

        return LNK_OK_ABSOLUTE_PATH ;
}

int main( int argc, char** argv ) {
        int file ;
        char buf[ 256 ] ;
    unsigned int len = sizeof( buf ) ;
    int ret ;

        if( argc >= 2 && ( file = open( argv[ 1 ], O_RDONLY | O_BINARY ) ) > 0 ) {
                ret = read_lnk( file, buf, &len ) ;
        printf( "Result: %s\n", lnk_err_message[ ret + LNK_ERROR_ADJUST ] )
;
        }
}

// CODE ENDS



Page was last modified "Jan 10 2012" The Rockbox Crew
aaa