/* * Interfacing library for Sony's obfuscated MP3 file format as used * on their network walkman devices * * Waider, March 2005 */ #include #include #include #include #include #include #include #include #include #include #include #include /* mp3 internals handling */ #include /* hardware abstraction layer, for obtaining the serial off the device */ #ifdef HAL_VERSION #include #if HAL_VERSION == 4 #define libhal_get_all_devices(x,y,z) hal_get_all_devices(x,y) #define libhal_device_property_exists(w,x,y,z) \ hal_device_property_exists(w,x,y) #define libhal_device_get_property_string(w,x,y,z) \ hal_device_get_property_string(w,x,y) #define libhal_device_get_property_bool(w,x,y,z) \ hal_device_get_property_bool(w,x,y) #define libhal_free_string(x) hal_free_string(x) #define libhal_free_string_array(x) hal_free_string_array(x) #endif #endif #include "libnw.h" #include "mple.h" #include "config.h" #include "common.h" #include "util.h" /****************************************************************************** * DEVICE MANIPULATION *****************************************************************************/ /* this is, obviously, undocumented */ gboolean mple_fix_checksum( nw_device_t *dev ) { mple_device *device = (mple_device *)dev; char *pblistpath; struct stat statbuf; pblist_hdr header; size_t cs = 0, size; FILE *pb = NULL; nw_attr_t *path_attr = NULL; /* sanity */ if ( dev == NULL || (( path_attr = nw_get_attr( dev, "mountpoint" )) == NULL ) || device->pbtemplate == NULL ) { errno = EINVAL; return FALSE; } pblistpath = g_strdup_printf( device->pbtemplate, NW_STR_ATTR( path_attr ), device->master_pblist ); /* * If the pblist file doesn't exist, bail out without error. It * probably just means the device is unformatted. */ if ( stat( pblistpath, &statbuf ) != 0 ) { g_free( pblistpath ); errno = ENOENT; goto out; } /* * on the other hand, if the file can't be opened, it *is* an error. */ if (( pb = fopen( pblistpath, "rb" )) == NULL ) { g_free( pblistpath ); goto out; } /* done with this */ g_free( pblistpath ); /* read file header */ if ( fread( &header, sizeof( pblist_hdr), 1, pb ) != 1 ) { goto out; } if ( memcmp( header.signature, "WMPLESYS", 8 ) != 0 ) { errno = EIO; /* as good as anything, really */ goto out; } /* verify the checksum */ cs = 0; for ( size = 0; size < 8; size++ ) { guint32 val = GUINT32_FROM_BE(((guint32 *)&header)[size]); cs ^= val; } if ( cs != 0 ) { /* your checksum is broken! */ fprintf( stderr, "checksum error: have %08x, expected %08x\n", GUINT32_FROM_BE(((guint32 *)&header)[7]), GUINT32_FROM_BE(((guint32 *)&header)[7]) ^ cs ); goto out; } out: if ( pb != NULL ) { fclose( pb ); } return cs; } /* * parse the master pblist file on DEVICE, populating the tracklist in * the device structure and returning a pointer to the tracklist. */ GList *mple_parse_pblist( nw_device_t *dev ) { mple_device *device = (mple_device *)dev; char *pblistpath; struct stat statbuf; pblist_hdr header; size_t cs, size, f; GList *node; FILE *pb = NULL; nw_folder_t *nfptr; nw_track_t *ntptr; GList *nflist = NULL; nw_attr_t *path_attr; dprintf( stderr, "%s: enter\n", __FUNCTION__ ); errno = 0; /* sanity */ if ( dev == NULL || (( path_attr = nw_get_attr( dev, "mountpoint" )) == NULL )) { errno = EINVAL; dprintf( stderr, "%s: bailing out, invalid params\n", __FUNCTION__ ); return NULL; } pblistpath = g_strdup_printf( device->pbtemplate, NW_STR_ATTR( path_attr ), device->master_pblist ); /* * If the pblist file doesn't exist, bail out without error. It * probably just means the device is unformatted. */ if ( stat( pblistpath, &statbuf ) != 0 ) { dprintf( stderr, "%s: %s not found\n", __FUNCTION__, pblistpath ); g_free( pblistpath ); errno = ENOENT; goto out; } /* * on the other hand, if the file can't be opened, it *is* an error. */ if (( pb = fopen( pblistpath, "rb" )) == NULL ) { dprintf( stderr, "%s: %s not readable\n", __FUNCTION__, pblistpath ); g_free( pblistpath ); goto out; } /* done with this */ g_free( pblistpath ); /* read file header */ if ( fread( &header, sizeof( pblist_hdr), 1, pb ) != 1 ) { dprintf( stderr, "%s: pblist not readable\n", __FUNCTION__ ); goto out; } if ( memcmp( header.signature, "WMPLESYS", 8 ) != 0 ) { dprintf( stderr, "%s: pblist corrupted\n", __FUNCTION__ ); errno = EIO; /* as good as anything, really */ goto out; } /* verify the checksum */ cs = 0; for ( size = 0; size < 8; size++ ) { guint32 val = GUINT32_FROM_BE(((guint32 *)&header)[size]); cs ^= val; } if ( cs != 0 ) { dprintf( stderr, "%s: pblist corrupted\n", __FUNCTION__ ); errno = EIO; /* again, close enough */ goto out; } /* extract any auxilliary information we're missing */ if (!(device->aux && MPLE_MSN )) { nw_attr_t *msn_attr = g_new0( nw_attr_t, 1 ); guint32 *msn = g_new0( guint32, 1 ); device->msn = GUINT32_FROM_BE( header.msn ); *msn = device->msn; msn_attr->type = NW_POINTER; /* fixme integer types */ msn_attr->data.ptr = msn; nw_set_attr( dev, "media serial number", msn_attr ); } if (!(device->aux && MPLE_TS )) { device->timestamp = fat_to_unix( GUINT32_FROM_BE( header.timestamp )); } if (!(device->aux && MPLE_MAGIC )) { device->magic = GUINT32_FROM_BE( header.magic ); } device->aux = MPLE_MAGIC | MPLE_TS | MPLE_MSN; /* * pull the folderlist */ for ( f = 0; f < GUINT32_FROM_BE( header.folders ); f++ ) { nfptr = g_new0( nw_folder_t, 1 ); nfptr->folderdata = g_new0( pblist_folder, 1 ); if (( size = fread( nfptr->folderdata, sizeof( pblist_folder ), 1, pb )) == 1 ) { nfptr->tracklist = NULL; nfptr->device = dev; nfptr->name = gchar_from_utf16be((char *)&(((pblist_folder *)nfptr->folderdata)->foldername), 126 ); nflist = g_list_append( nflist, nfptr ); continue; } /* fall through */ dprintf( stderr, "%s: pblist read failed\n", __FUNCTION__ ); errno = EIO; g_free( nfptr ); goto bailout; } /* Pull the MP3 file data */ /* sanity check: are we looking at the first folder offset? */ node = nflist; if ( node != NULL ) { /* careful! */ nfptr = node->data; if ( ftell( pb ) != GUINT32_FROM_BE( FOLDER_OFFSET( nfptr->folderdata ))) { dprintf( stderr, "%s: failed pblist sanity check\n", __FUNCTION__ ); errno = ENODATA; goto bailout; } } /* get tracklists */ /* this could be merged with the above, but it would require file seeking. doing it this way allows contiguous reading */ for ( f = 0; f < GUINT32_FROM_BE( header.folders ); f++ ) { long start, end; guint32 tracks, t; nw_folder_t *enfptr; node = g_list_nth( nflist, f ); nfptr = node->data; start = GUINT32_FROM_BE( FOLDER_OFFSET( nfptr->folderdata )); if ( f == GUINT32_FROM_BE( header.folders ) - 1 ) { node = g_list_nth( nflist, 0 ); enfptr = node->data; end = GUINT32_FROM_BE( FOLDER_OFFSET( enfptr->folderdata )) + ( GUINT32_FROM_BE( header.tracks ) * 2 ); } else { node = g_list_nth( nflist, f + 1 ); enfptr = node->data; end = GUINT32_FROM_BE( FOLDER_OFFSET( enfptr->folderdata )); } tracks = ( end - start ) / 2; nfptr->tracklist = NULL; for ( t = 0; t < tracks; t++ ) { ntptr = g_new0( nw_track_t, 1 ); if ( fread( &(ntptr->tracknum ), sizeof( guint16 ), 1, pb ) == 1 ) { ntptr->tracknum = GUINT16_FROM_BE( ntptr->tracknum ); ntptr->parent = nfptr; ntptr->device = dev; nfptr->tracklist = g_list_append( nfptr->tracklist, ntptr ); } else { goto bailout; } } } /* skip over padding */ if ( GUINT32_FROM_BE( header.tracks ) % 8 ) { if ( fseek( pb, ( 8 - ( GUINT32_FROM_BE( header.tracks ) % 8 )) * 2, SEEK_CUR ) != 0 ) { goto bailout; } } /* get metadata */ for ( f = 0; f < GUINT32_FROM_BE( header.folders ); f++ ) { guint16 t = 0; node = g_list_nth( nflist, f ); nfptr = node->data; while(( node = g_list_nth( nfptr->tracklist, t )) != NULL ) { size_t bytes; ntptr = node->data; ntptr->trackdata = g_new0( pblist_track, 1 ); if (( bytes = fread( ntptr->trackdata, 1, sizeof( pblist_track ), pb )) == sizeof( pblist_track )) { id3_utf8_t *title = utf8_from_utf16be(( char * )&((( pblist_track *)ntptr->trackdata )->title ), 128 ); id3_utf8_t *artist = utf8_from_utf16be(( char * )&((( pblist_track *)ntptr->trackdata )->artist ), 128 ); /* fill out the NW data */ ntptr->fullpath = g_strdup_printf( device->mptemplate, NW_STR_ATTR( path_attr ), ntptr->tracknum ); ntptr->filename = gchar_from_utf16be(( char * )&((( pblist_track *)ntptr->trackdata )->filename ), 128 ); nw_set_tag( ntptr, ID3_FRAME_TITLE, title ); g_free( title ); nw_set_tag( ntptr, ID3_FRAME_ARTIST, artist ); g_free( artist ); t++; continue; } /* FALL THROUGH */ if ( feof( pb )) { dprintf( stderr, "%s: end of pblist reached when reading folder %d\n", __FUNCTION__, f ); errno = ENODATA; } else { if ( errno == 0 ) { errno = EIO; } } goto bailout; } } out: if ( pb != NULL ) { fclose( pb ); } dev->folderlist = nflist; dprintf( stderr, "%s: exit, folderlist is %p, errno is %d\n", __FUNCTION__, nflist, errno ); return nflist; /* clean up the mess and return NULL */ bailout: if ( nflist != NULL ) { node = nflist; while( node ) { nfptr = node->data; if ( nfptr->tracklist != NULL ) { GList *tnode = nfptr->tracklist; while( tnode ) { g_free( tnode->data ); tnode = g_list_next( tnode ); } g_list_free( nfptr->tracklist ); } g_free( nfptr ); node = g_list_next( node ); } g_list_free( nflist ); nflist = NULL; } goto out; } /* * synchronize the in-memory version of DEVICE to the device */ gboolean mple_dev_sync( nw_device_t *dev ) { mple_device *device = (mple_device *)dev; gboolean retval = FALSE; gchar *pbfile0 = NULL; gchar *pbfile1 = NULL; pblist_hdr header; gboolean rollback = FALSE; FILE *pb = NULL; guint32 cs; int i; guint32 tracklist; guint16 *tracks = NULL; guint16 trackcnt; struct stat statbuf; gchar *dirname; nw_attr_t *path_attr = NULL; dprintf( stderr, "%s: enter\n", __FUNCTION__ ); if ( dev == NULL || (( path_attr = nw_get_attr( dev, "mountpoint" )) == NULL )) { goto out; } /* very rough */ if ( strlen( NW_STR_ATTR( path_attr ) ) > PATH_MAX - strlen( device->pbtemplate )) { goto out; } pbfile1 = g_strdup_printf( device->pbtemplate, NW_STR_ATTR( path_attr ), device->master_pblist ); if ( device->master_pblist > 0 ) { pbfile0 = g_strdup_printf( device->pbtemplate, NW_STR_ATTR( path_attr ), device->master_pblist - 1 ); } /* set up the header */ memset( &header, 0, sizeof( pblist_hdr )); memcpy( header.signature, "WMPLESYS", 8 ); /* the timestamp field never changes once it's written */ if ( device->aux & MPLE_TS ) { header.timestamp = GUINT32_TO_BE( unix_to_fat( device->timestamp )); } else { /* to be 100% accurate, we should stat the ESYS directory and use its ctime. Since it's not actually a show-stopper, I've not bothered. */ header.timestamp = GUINT32_TO_BE( unix_to_fat( time( NULL ))); } header.msn = GUINT32_TO_BE( device->msn ); memcpy( &header.magic, PBLIST_MAGIC, sizeof( PBLIST_MAGIC )); if ( dev->folderlist != NULL ) { GList *node; for ( node = dev->folderlist; node != NULL ; node = node->next ) { nw_folder_t *fptr = node->data; header.folders++; if ( fptr->tracklist != NULL ) { GList *tnode; for ( tnode = fptr->tracklist; tnode != NULL; tnode = tnode->next ) { nw_track_t *tptr = tnode->data; if ( header.tracks % 8 == 0 ) { guint16 *tmp; tmp = g_realloc( tracks, sizeof( guint16 ) * 8 * (( header.tracks / 8 ) + 1 )); tracks = tmp; } tracks[header.tracks++] = GUINT16_TO_BE( tptr->tracknum ); } } else { /* nothing to write */ } } } /* these need to be byteswapped before going onto the device */ header.folders = GUINT32_TO_BE( header.folders ); header.tracks = GUINT32_TO_BE( header.tracks ); cs = 0; for ( i = 0; i < 7; i++ ) { guint32 val = GUINT32_TO_BE(((guint32 *)&header)[i]); cs ^= val; } header.checksum = GUINT32_TO_BE( cs ); /* now we touch the disk */ dprintf( stderr, "%s: starting disk activity\n", __FUNCTION__ ); dirname = g_dirname( pbfile1 ); if ( mkpath( dirname, 0755 ) == FALSE ) { goto out; } g_free( dirname ); if ( stat( pbfile1, &statbuf ) == 0 ) { if ( pbfile0 != NULL ) { unlink( pbfile0 ); /* don't care if it fails */ if ( rename( pbfile1, pbfile0 ) != 0 ) { goto out; } /* from this point on we can do a rollback if required */ rollback = TRUE; } } if (( pb = fopen( pbfile1, "wb" )) == NULL ) { goto rollback; } if ( fwrite( &header, sizeof( pblist_hdr ), 1, pb ) != 1 ) { goto rollback; } if ( dev->folderlist != NULL ) { guint16 folder; tracklist = sizeof( pblist_hdr ) + sizeof( pblist_folder ) * g_list_length( dev->folderlist ); GList *node; nw_folder_t *fptr; for ( folder = 0; ( node = g_list_nth( dev->folderlist, folder )) != NULL; folder++ ) { dprintf( stderr, "%s: writing folder %d\n", __FUNCTION__, folder ); fptr = node->data; trackcnt = g_list_length( fptr->tracklist ); ((pblist_folder *)fptr->folderdata)->offset = GUINT32_TO_BE( tracklist ); if ( fwrite( fptr->folderdata, sizeof( pblist_folder ), 1, pb ) != 1 ) { goto rollback; } tracklist += sizeof( guint16 ) * trackcnt; } /* next, the tracklist */ trackcnt = GUINT32_TO_BE( header.tracks ); if ( trackcnt ) { if ( fwrite( tracks, sizeof( guint16 ), trackcnt, pb ) != trackcnt ) { goto rollback; } /* padding */ while( trackcnt++ % 8 ) { guint16 pad = 0; if ( fwrite( &pad, sizeof( guint16 ), 1, pb ) != 1 ) { goto rollback; } } /* now do the actual track data */ for ( folder = 0; ( node = g_list_nth( dev->folderlist, folder )) != NULL; folder++ ) { fptr = node->data; if ( fptr->tracklist != NULL ) { nw_track_t *tptr; for ( trackcnt = 0; ( node = g_list_nth( fptr->tracklist, trackcnt )) != NULL; trackcnt++ ) { tptr = node->data; /* code is 0-based, api is 1-based */ dprintf( stderr, "Writing trackdata for track %d (%p)\n", trackcnt + 1, tptr ); if ( fwrite( tptr->trackdata, sizeof( pblist_track ), 1, pb ) != 1 ) { goto rollback; } } } } } else { /* no tracks to write */ dprintf( stderr, "%s: no tracks!\n", __FUNCTION__ ); } } /* yay! */ retval = TRUE; dev->dirty = FALSE; out: if ( tracks != NULL ) { g_free( tracks ); } if ( pb != NULL ) { fclose( pb ); } if ( pbfile0 != NULL ) { g_free( pbfile0 ); } if ( pbfile1 != NULL ) { g_free( pbfile1 ); } dprintf( stderr, "%s: exit\n", __FUNCTION__ ); return retval; rollback: /* if these fail, you're screwed either way. */ if ( rollback == TRUE ) { int old_errno = errno; /* preserve */ unlink( pbfile1 ); rename( pbfile0, pbfile1 ); errno = old_errno; } goto out; } /* * create a new device structure based at DEVPATH */ /* forward declaration... */ static guint16 mple_dev_get_next_trackno( nw_device_t * ); nw_device_t *mple_dev_init( char *devpath ) { nw_device_t *dev = NULL; mple_device *device = NULL; device = g_malloc0( sizeof( mple_device )); dev = (nw_device_t *)device; /* set up function pointers */ dev->dev_free = mple_dev_free; dev->dev_sync = mple_dev_sync; dev->parse_directory = mple_parse_pblist; dev->add_folder = mple_add_folder; dev->del_folder = mple_del_folder; dev->add_to_folder = mple_add_to_folder; dev->del_track = mple_del_track; dev->get_track_size = mple_track_size; dev->get_track_time = mple_track_time; dev->get_track_frames = mple_track_frames; dev->get_next_track_number = mple_dev_get_next_trackno; /* filesystem functions */ dev->stat = mple_stat; dev->fopen = mple_open; dev->fclose = mple_close; dev->fseek = mple_seek; dev->ftell = mple_tell; dev->fread = mple_read; dev->fwrite = mple_write; dev->api = NW_MPLE; dev->progress = NULL; device->msn = 0; device->aux = 0; device->pbtemplate = NULL; device->master_pblist = DEFAULT_MASTER_PBLIST; device->mptemplate = NULL; dev->folderlist = NULL; /* for great justice, or compatibility with earlier code, or, I dunno, making people's lives easier */ dev->autosync = TRUE; dev->dirty = FALSE; if ( devpath != NULL ) { nw_attr_t *path_attr = g_new0( nw_attr_t, 1 ); path_attr->type = NW_STRING; if (( path_attr->data.gc = g_strdup( devpath )) == NULL ) { mple_dev_free( dev ); device = NULL; goto out; } nw_set_attr( dev, "mountpoint", path_attr ); } if (( device->pbtemplate = g_strdup( DEFAULT_PB_TEMPLATE )) == NULL ) { mple_dev_free( dev ); device = NULL; goto out; } if (( device->mptemplate = g_strdup( DEFAULT_MP_TEMPLATE )) == NULL ) { mple_dev_free( dev ); device = NULL; goto out; } out: dprintf( stderr, "allocated device %p\n", dev ); return dev; } /* * similar to the above, but takes a /dev argument and attempts to * mount the device if it's not already available. * Not yet finished. */ mple_device *mple_dev_mount( char *devpath ) { mple_device *device = NULL; /* approximately: - stat the device - check /proc/mounts? for device mount - if not mounted, do a mount, then recheck - call mple_dev_init( mountpoint ) - if root, get msn - cache msn on the device? */ return device; } /* * free up all memory associated with DEVICE * do not use the pointer after you've called this! * NB cleans up only the MPLE portion (private cleanup) */ void mple_dev_free( nw_device_t *dev ) { mple_device *device = (mple_device *)dev; dprintf( stderr, "%s: enter (dev = %p)\n", __FUNCTION__, dev ); if ( device != NULL ) { if ( device->pbtemplate != NULL ) { g_free( device->pbtemplate ); } if ( device->mptemplate != NULL ) { g_free( device->mptemplate ); } } /* clean up private folder data only */ if ( dev->folderlist != NULL ) { GList *node = dev->folderlist; nw_folder_t *nfptr; while( node ) { nfptr = node->data; if ( nfptr->tracklist != NULL ) { GList *tnode = nfptr->tracklist; while( tnode ) { nw_track_t *ntptr = tnode->data; if ( ntptr->trackdata != NULL ) { g_free( ntptr->trackdata ); ntptr->trackdata = NULL; } tnode = g_list_next( tnode ); } } if ( nfptr->folderdata != NULL ) { g_free( nfptr->folderdata ); nfptr->folderdata = NULL; } node = g_list_next( node ); } } dprintf( stderr, "%s: exit\n", __FUNCTION__ ); } /* * find the next available track number on DEVICE */ static guint16 mple_dev_get_next_trackno( nw_device_t *dev ) { guint16 trackno = 1; GList *node; nw_folder_t *fptr; if ( dev->folderlist == NULL ) { errno = EINVAL; goto out; } FOLDERS: node = dev->folderlist; while( node != NULL ) { GList *tnode; nw_track_t *tptr; fptr = node->data; tnode = fptr->tracklist; while( tnode != NULL ) { tptr = tnode->data; if ( tptr->tracknum == trackno ) { trackno++; /* reset folderlist */ goto FOLDERS; } tnode = g_list_next( tnode ); } node = g_list_next( node ); } if ( trackno > MAX_TRACKS ) { trackno = 0; errno = ENOSPC; } out: return trackno; } /****************************************************************************** * FOLDER MANIPULATION *****************************************************************************/ /* * add folder: update local data */ guint32 mple_add_folder( nw_device_t *dev, gchar *name, guint32 pos ) { pblist_folder *pfolder = NULL; guint16 *pbname = NULL; size_t convlen; nw_folder_t *folder = g_list_nth( dev->folderlist, pos - 1 )->data; pfolder = folder->folderdata = g_new0( pblist_folder, 1 ); pbname = gchar_to_utf16be( name, 126, &convlen ); memcpy( &pfolder->foldername, pbname, convlen ); g_free( pbname ); return pos; } /* * Nuke a folder on DEVICE at POS * Caution: will delete folder contents also! */ gboolean mple_del_folder( nw_device_t *dev, guint32 pos ) { nw_folder_t *fptr; fptr = g_list_nth_data( dev->folderlist, pos - 1 ); if ( fptr->folderdata != NULL ) { g_free( fptr->folderdata ); } fptr->folderdata = NULL; return TRUE; } /* * change the position of DEVICE's FOLDER'th folder to POS */ guint32 mple_mv_folder( nw_device_t *dev, guint16 folder, guint16 pos ) { return pos; } /* * rename DEVICE's FOLDER'th folder to the name in NEWNAME */ guint32 mple_ren_folder( nw_device_t *dev, guint32 folder, gchar *newname ) { pblist_folder *pfolder = NULL; guint16 *pbname = NULL; size_t convlen; nw_folder_t *fptr = g_list_nth( dev->folderlist, folder - 1 )->data; if ( fptr->folderdata == NULL ) { fptr->folderdata = g_new0( pblist_folder, 1 ); } pfolder = fptr->folderdata; pbname = gchar_to_utf16be( newname, 126, &convlen ); memcpy( &pfolder->foldername, pbname, convlen ); g_free( pbname ); return folder; } /* * Add a track to a folder * * Adds a track to DEVICE in the FOLDER'th folder. TRACK contains the * metadata, and TRACKNUM is the track number used to generate the * conversion matrix. POS is the position within the folder; if zero, * the track is appended to the end of the existing tracklist. This * assumes that the track has already been written to the device. * * new API: this just copies the metadata into a pblist_track structure */ guint32 mple_add_to_folder( nw_device_t *dev, guint32 folder, nw_track_t *track, guint32 tracknum, guint32 pos ) { nw_folder_t *fptr; guint16 *conv = NULL; size_t convlen; id3_utf8_t *title = NULL, *artist = NULL; dprintf( stderr, "%s: enter, track data %p\n", __FUNCTION__, track ); fptr = g_list_nth( dev->folderlist, folder - 1 )->data; if ( track == NULL ) { /* shouldn't be? */ dprintf( stderr, "we're HERE!\n" ); abort(); track = g_list_nth( fptr->tracklist, pos - 1 )->data; } if ( track->trackdata == NULL ) { track->trackdata = g_new0( pblist_track, 1 ); } if ( track->filename != NULL ) { dprintf( stderr, "%s: loaded filename with %s\n", __FUNCTION__, track->filename ); conv = gchar_to_utf16be( track->filename, 127, &convlen ); memcpy( &((pblist_track *)track->trackdata)->filename, conv, convlen ); g_free( conv ); dev->dirty = TRUE; } artist = nw_get_tag( track, ID3_FRAME_ARTIST ); if ( artist != NULL ) { dprintf( stderr, "%s: loaded artist with %s\n", __FUNCTION__, artist ); conv = gchar_to_utf16be( track, 127, &convlen ); memcpy( &((pblist_track *)track->trackdata)->artist, conv, convlen ); g_free( conv ); dev->dirty = TRUE; } title = nw_get_tag( track, ID3_FRAME_TITLE ); if ( title != NULL ) { dprintf( stderr, "%s: loaded title with %s\n", __FUNCTION__, title ); conv = utf8_to_utf16be( title, 127, &convlen ); memcpy( &((pblist_track *)track->trackdata)->title, conv, convlen ); g_free( conv ); dev->dirty = TRUE; } return pos; } /****************************************************************************** * TRACK MANIPULATION *****************************************************************************/ /* * Build the conversion array for track TRACKNUM in CONV. * * There may be a cleaner/smarter way to do this - perhaps as a * function rather than an array, for example. * * This iterates through the bits from bit 0 to the highest bit * required to represent the track number. If the bit N is set, then * every 2N bytes in the file are taken as a block, the two halves of * which are swapped. The initial conversion matrix is simply a * straight ( 256 - bytevalue ) array. * * After generating, each value is XOR'd with a bitflipped version of * the last octet of the media serial number. */ void mple_build_conv_array( guint16 trackno, guint8 *conv, guint8 xorvalue ) { guint16 bit; guint16 i; for ( i = 0; i < 256; i++ ) { conv[i] = 255 - i; } bit = 1; while( bit <= trackno ) { if ( trackno & bit ) { guint16 j; guint16 k; for ( j = 0; j < 256; j+= bit * 2 ) { for ( k = 0; k < bit; k++ ) { guint8 temp; temp = conv[j + k]; conv[j + k] = conv[j + k + bit]; conv[j + k + bit] = temp; } } } bit <<= 1; } /* apply the XOR */ for ( i = 0; i < 256; i++ ) { conv[i] = conv[i] ^ ( ~xorvalue & 0xff ); } } /* * delete from DEVICE track TRACKNUM */ gboolean mple_del_track( nw_device_t *dev, guint32 tracknum ) { nw_track_t *tptr; int retval = FALSE; mple_device *device = (mple_device *)dev; gchar *filename; nw_attr_t *path_attr = NULL; dprintf( stderr, "%s: enter (nuking %d)\n", __FUNCTION__, tracknum ); /* fixme assert? */ tptr = nw_get_track_ptr( dev, tracknum ); if ( tptr == NULL ) { goto out; } path_attr = nw_get_attr( dev, "mountpoint" ); filename = g_strdup_printf( device->mptemplate, NW_STR_ATTR( path_attr ), tptr->tracknum ); dprintf( stderr, "%s: unlinking %s\n", __FUNCTION__, filename ); unlink( filename ); g_free( filename ); retval = TRUE; if ( tptr->trackdata != NULL ) { dprintf( stderr, "%s: nuking %p\n", __FUNCTION__, tptr->trackdata ); g_free( tptr->trackdata ); tptr->trackdata = NULL; } out: dprintf( stderr, "%s: exit %s\n", __FUNCTION__, retval == TRUE ? "true" : "false" ); return retval; } /* find all likely devices */ #ifdef HAL_VERSION /* * use HAL to do the job. Some code duplication here, but screw * it. Also some clunkiness, but screw that, too. */ GList *mple_get_nw_devs_from_hal( gchar *magic ) { #if HAL_VERSION == 5 DBusError error; DBusConnection *dc = NULL; #endif LibHalContext *hc = NULL; GList *retval = NULL; char **alldevs = NULL; int i, n; dprintf( stderr, "asking HAL for MSN\n" ); #if HAL_VERSION == 5 dbus_error_init( &error ); if (( dc = dbus_bus_get( DBUS_BUS_SYSTEM, &error )) == NULL ) { goto out; } if (( hc = libhal_ctx_new()) == NULL ) { goto out; } if ( !libhal_ctx_set_dbus_connection( hc, dc )) { goto out; } if ( !libhal_ctx_init( hc, &error )) { goto out; } #else if (( hc = hal_initialize( NULL, FALSE )) == NULL ) { goto out; } #endif if (( alldevs = libhal_get_all_devices( hc, &n, &error )) == NULL ) { goto out; } for ( i = 0; i < n; i++ ) { if ( libhal_device_property_exists( hc, alldevs[i], "volume.uuid", &error )) { char *parent = libhal_device_get_property_string( hc, alldevs[i], "block.storage_device", &error ); char *dvpt = libhal_device_get_property_string( hc, alldevs[i], "block.device", &error ); dbus_bool_t mounted = libhal_device_get_property_bool( hc, alldevs[i], "volume.is_mounted", &error ); char *mtpt = NULL; if ( mounted ) { mtpt = libhal_device_get_property_string( hc, alldevs[i], "volume.mount_point", &error ); } if (( parent != NULL ) && ( strlen( parent ) >= strlen( magic )) && ( strcmp( &parent[strlen( parent ) - strlen( magic )], magic ) == 0 )) { nw_device_t *dev = mple_dev_init( mtpt ); nw_attr_t *dvpt_attr = g_new0( nw_attr_t, 1 ); dvpt_attr->type = NW_STRING; dvpt_attr->data.gc = g_strdup( dvpt ); nw_set_attr( dev, "device", dvpt_attr ); retval = g_list_append( retval, dev ); } if ( dvpt != NULL ) { libhal_free_string( dvpt ); } if ( mtpt != NULL ) { libhal_free_string( mtpt ); } if ( parent != NULL ) { libhal_free_string( parent ); } } } out: if ( alldevs != NULL ) { libhal_free_string_array( alldevs ); } if ( hc != NULL ) { /* not sure about these - they may be covered by safeguards in HAL */ /* -> if hal is init'd, uninit it */ /* -> if hal is connected, disconnect it */ #if HAL_VERSION == 5 libhal_ctx_shutdown( hc, &error ); /* free hal ctx */ libhal_ctx_free( hc ); #else hal_shutdown( hc ); #endif } #if HAL_VERSION == 5 /* clean up dbus */ if ( dc != NULL ) { #if HAVE_LIBDBUS_1 dbus_connection_disconnect( dc ); #endif dbus_connection_unref( dc ); } dbus_error_free( &error ); #endif return retval; } #endif /* auto-probe a nw device */ #ifdef HAL_VERSION /* * use HAL to do the job. Need to be corrected by a real C guy :( */ GList *mple_probe_nw_devs_from_hal() { #if HAL_VERSION == 5 DBusError error; DBusConnection *dc = NULL; #endif LibHalContext *hc = NULL; GList *retval = NULL; char **alldevs = NULL; char *model = NULL; char *nw_udi = NULL; char *parent_udi = NULL; int i, j, n; dprintf( stderr, "searching HAL for MPLE devices\n" ); #if HAL_VERSION == 5 dbus_error_init( &error ); if (( dc = dbus_bus_get( DBUS_BUS_SYSTEM, &error )) == NULL ) { goto out; } if (( hc = libhal_ctx_new()) == NULL ) { goto out; } if ( !libhal_ctx_set_dbus_connection( hc, dc )) { goto out; } if ( !libhal_ctx_init( hc, &error )) { goto out; } #else if (( hc = hal_initialize( NULL, FALSE )) == NULL ) { goto out; } #endif if (( alldevs = libhal_get_all_devices( hc, &n, &error )) == NULL ) { goto out; } /* Ugly algo : walk and walk */ /* The alldevs isn't sorted hierarchicaly (at least the lshal command isn't */ /* Perhaps there's some find functions in libhal, but for the moment, */ /* to preserve 0.4/0.5 compatibility, use basic ones */ /* First, search a NW device */ for ( i = 0; i < n; i++ ) { if ( libhal_device_property_exists( hc, alldevs[i], "storage.model", &error )) { model = libhal_device_get_property_string( hc, alldevs[i], "storage.model", &error ); if ( model != NULL && strlen( model )) { dprintf( stderr, "found %s\n", model ); } if (( model != NULL ) && ( strlen( model ) == strlen( HAL_MAGIC )) && ( strcmp( model, HAL_MAGIC ) == 0 )) { /* Got one */ /* Could be double-checked with storage.bus='usb' and storage.vendor='SONY' */ nw_udi = libhal_device_get_property_string( hc, alldevs[i], "info.udi", &error ); /* Now, find the attached partition */ for ( j = 0; j < n; j++ ) { /* computer don't have parent... */ if ( libhal_device_property_exists( hc, alldevs[j], "info.parent", &error )) { parent_udi = libhal_device_get_property_string( hc, alldevs[j], "info.parent", &error ); if (( parent_udi != NULL ) && ( strlen( parent_udi ) == strlen( nw_udi )) && ( strcmp( parent_udi, nw_udi ) == 0 )) { /* Got the partition */ char *dvpt = libhal_device_get_property_string( hc, alldevs[j], "block.device", &error ); dbus_bool_t mounted = libhal_device_get_property_bool( hc, alldevs[j], "volume.is_mounted", &error ); char *mtpt = NULL; dprintf( stderr, "found %s\n", parent_udi ); /* For the moment, only care about pre-mounted devices */ if ( mounted ) { mtpt = libhal_device_get_property_string( hc, alldevs[j], "volume.mount_point", &error ); nw_device_t *dev = mple_dev_init( mtpt ); nw_attr_t *dvpt_attr = g_new0( nw_attr_t, 1 ); dvpt_attr->type = NW_STRING; dvpt_attr->data.gc = g_strdup( dvpt ); nw_set_attr( dev, "device", dvpt_attr ); retval = g_list_append( retval, dev ); } else { dprintf( stderr, "it's not mounted\n" ); } if ( dvpt != NULL ) { libhal_free_string( dvpt ); } if ( mtpt != NULL ) { libhal_free_string( mtpt ); } } } if ( parent_udi != NULL ) { libhal_free_string( parent_udi ); parent_udi = NULL; } } } } /* clean up */ if ( model != NULL ) { libhal_free_string( model ); model = NULL; } if ( nw_udi != NULL ) { libhal_free_string( nw_udi ); nw_udi = NULL; } } out: if ( alldevs != NULL ) { libhal_free_string_array( alldevs ); } if ( hc != NULL ) { /* not sure about these - they may be covered by safeguards in HAL */ /* -> if hal is init'd, uninit it */ /* -> if hal is connected, disconnect it */ #if HAL_VERSION == 5 libhal_ctx_shutdown( hc, &error ); /* free hal ctx */ libhal_ctx_free( hc ); #else hal_shutdown( hc ); #endif } #if HAL_VERSION == 5 /* clean up dbus */ if ( dc != NULL ) { #if HAVE_LIBDBUS_1 dbus_connection_disconnect( dc ); #endif dbus_connection_unref( dc ); } dbus_error_free( &error ); #endif return retval; } #endif /* * API entry-point */ GList *mple_get_nw_devs( gchar *magic, GList *retval ) { #ifdef HAL_VERSION if ( magic == NULL ) { retval = mple_probe_nw_devs_from_hal (); } else { retval = mple_get_nw_devs_from_hal( magic ); } #else fprintf( stderr, "warning: no probing available\n" ); #endif return retval; } /* * retrieve the media serial number. requires root privileges if you * don't have HAL, and there doesn't appear to be any way for me to * get around that. */ #ifdef HAL_VERSION /* * Use HAL to find the device and fetch its MSN and any other data * we're missing. */ gboolean mple_get_msn_from_hal( nw_device_t *dev ) { mple_device *device = (mple_device *)dev; nw_attr_t *path_attr = NULL; #if HAL_VERSION == 5 DBusError error; DBusConnection *dc = NULL; #endif gboolean retval = FALSE; char **alldevs = NULL; int n, i; char *dvpt = NULL, *mtpt = NULL, *uuid = NULL; gboolean mtpt_gfree = FALSE; LibHalContext *hc = NULL; path_attr = nw_get_attr( dev, "mountpoint" ); #if HAL_VERSION == 5 dbus_error_init( &error ); if (( dc = dbus_bus_get( DBUS_BUS_SYSTEM, &error )) == NULL ) { goto out; } if (( hc = libhal_ctx_new()) == NULL ) { goto out; } if ( !libhal_ctx_set_dbus_connection( hc, dc )) { goto out; } if ( !libhal_ctx_init( hc, &error )) { goto out; } #else if (( hc = hal_initialize( NULL, FALSE )) == NULL ) { goto out; } #endif /* phew. now we can actually ask HAL what's on the system */ if (( alldevs = libhal_get_all_devices( hc, &n, &error )) == NULL ) { goto out; } for ( i = 0; i < n; i++ ) { if ( libhal_device_property_exists( hc, alldevs[i], "volume.uuid", &error )) { mtpt = libhal_device_get_property_string( hc, alldevs[i], "volume.mount_point", &error ); uuid = libhal_device_get_property_string( hc, alldevs[i], "volume.uuid", &error ); dvpt = libhal_device_get_property_string( hc, alldevs[i], "block.device", &error ); /* validate uuid very briefly: set and 9 chars long (that's 8 chars of uuid plus a dash in the middle. */ if ( uuid != NULL && strlen( uuid ) == 9 ) { if (( dev != NULL ) && ( dvpt != NULL )) { nw_attr_t *dvpt_attr = nw_get_attr( dev, "device" ); if ( dvpt_attr && ( strcmp( NW_STR_ATTR( dvpt_attr ), dvpt ) == 0 )) { retval = TRUE; } } /* HAL has been known to lie about mounted devices */ if ( mtpt == NULL || strlen( mtpt ) == 0 ) { FILE *pm; gchar *mounts = NULL; gchar buf[BUFSIZ]; size_t size, msize = 0; /* hope you don't have too many mounts... */ if (( pm = fopen( "/proc/mounts", "rb" )) != NULL ) { while(( size = fread( buf, 1, BUFSIZ, pm )) > 0 ) { mounts = g_realloc( mounts, msize + size ); memcpy( &mounts[msize], buf, size ); msize += size; } if ( msize > 0 ) { gchar *d = g_strstr_len( mounts, msize, dvpt ); if ( d != NULL && d + strlen( dvpt ) + 1 < mounts + msize ) { size_t l; d += strlen( dvpt ) + 1; for ( l = 0; d + l < mounts + msize && d[l] != ' '; l++ ); d[l] = '\0'; mtpt = g_strdup( d ); mtpt_gfree = TRUE; } } if ( mounts != NULL ) { g_free( mounts ); } fclose( pm ); } } if (( NW_STR_ATTR( path_attr ) != NULL ) && ( mtpt != NULL ) && ( strcmp( NW_STR_ATTR( path_attr ), mtpt ) == 0 )) { retval = TRUE; } } if ( retval == TRUE ) { nw_attr_t *msn_attr = g_new0( nw_attr_t, 1 ); nw_attr_t *dvpt_attr = nw_get_attr( dev, "device" ); guint32 *msn = g_new0( guint32, 1 ); guint8 c; /* may as well fill in some other bits */ if ( dvpt_attr == NULL ) { dvpt_attr = g_new0( nw_attr_t, 1 ); dvpt_attr->type = NW_STRING; dvpt_attr->data.gc = g_strdup( dvpt ); nw_set_attr( dev, "device", dvpt_attr ); } if ( NW_STR_ATTR( path_attr ) == NULL && mtpt != NULL ) { NW_STR_ATTR( path_attr ) = g_strdup( mtpt ); } /* unpack the UUID into something I can use. This is a little nasty, but saves me using something like gscanner to get the job done. */ memmove( &(uuid[4]), &(uuid[5]), 5 ); /* overwrite the dash */ device->msn = 0; /* safety clown says */ for ( c = 0; c < 8; c++ ) { char v = uuid[c]; if ( v > 'F' ) { v -= ( 'a' - 10 ); } else if ( v > '9' ) { v -= ( 'A' - 10 ); } else { v -= '0'; } device->msn |= ( v << (( 7 - c )* 4 )); } device->msn = GUINT32_FROM_BE( device->msn ); *msn = device->msn; msn_attr->type = NW_POINTER; /* fixme integer types */ msn_attr->data.ptr = msn; nw_set_attr( dev, "media serial number", msn_attr ); } if ( dvpt != NULL ) { libhal_free_string( dvpt ); } if ( mtpt != NULL ) { if ( mtpt_gfree ) { g_free( mtpt ); } else { libhal_free_string( mtpt ); } } if ( uuid != NULL ) { libhal_free_string( uuid ); } if ( retval == TRUE ) { break; } } } out: if ( alldevs != NULL ) { libhal_free_string_array( alldevs ); } if ( hc != NULL ) { /* not sure about these - they may be covered by safeguards in HAL */ /* -> if hal is init'd, uninit it */ /* -> if hal is connected, disconnect it */ #if HAL_VERSION == 5 libhal_ctx_shutdown( hc, &error ); /* free hal ctx */ libhal_ctx_free( hc ); #else hal_shutdown( hc ); #endif } #if HAL_VERSION == 5 /* clean up dbus */ if ( dc != NULL ) { #if HAVE_LIBDBUS_1 dbus_connection_disconnect( dc ); #endif dbus_connection_unref( dc ); } #endif return retval; } #endif gboolean mple_get_media_serial_number( nw_device_t *dev ) { mple_device *device = (mple_device *)dev; FILE *d = NULL; gboolean retval = FALSE; nw_attr_t *dvpt_attr = NULL; nw_attr_t *path_attr = NULL; if ( device == NULL ) { errno = EINVAL; } dvpt_attr = nw_get_attr( dev, "device" ); path_attr = nw_get_attr( dev, "mountpoint" ); if ( device->aux & MPLE_MSN ) { retval = TRUE; goto out; } /* no device information -> we're doomed */ if ( dvpt_attr == NULL && path_attr == NULL ) { errno = EINVAL; goto out; } #ifdef HAL_VERSION if (( retval = mple_get_msn_from_hal( dev ))) { goto out; } /* else fall through */ #endif /* attempt #2: see if there's a pblist file we can mooch off */ device->aux &= ~MPLE_MSN; if ( mple_parse_pblist( dev ) != NULL ) { if ( device->aux & MPLE_MSN ) { retval = TRUE; goto out; } } if ( dvpt_attr == NULL ) { errno = EINVAL; goto out; } /* attempt #3: raw device access */ if (( d = fopen( NW_STR_ATTR( dvpt_attr ), "rb" )) == NULL ) { goto out; } /* 0x27 = offset to serial number in boot sector */ if ( fseek( d, 0x27, SEEK_SET ) != 0 ) { goto out; } if (( fread( &(device->msn), sizeof( device->msn ), 1, d )) != 1 ) { goto out; } /* don't forget to byte-swap it */ { nw_attr_t *msn_attr = g_new0( nw_attr_t, 1 ); guint32 *msn = g_new0( guint32, 1 ); device->msn = GUINT32_FROM_BE( device->msn ); *msn = device->msn; msn_attr->type = NW_POINTER; /* fixme integer types */ msn_attr->data.ptr = msn; nw_set_attr( dev, "media serial number", msn_attr ); } out: if ( d != NULL ) { fclose( d ); } return retval; } /* * convert a WMMP file back to an MP3 file, writing it to fd */ int mple_convert_wmmp( nw_track_t *track, int fd ) { FILE *mp, *out; struct stat statbuf; mpdat_hdr header; guint8 buffer[BUFSIZ]; guint8 conv[256]; size_t i; char *mpdat = NULL; /* xxx */ int trackno = 0; if ( stat( mpdat, &statbuf ) != 0 ) { return 0; } if (( mp = fopen( mpdat, "rb" )) == NULL ) { return 0; } if (( out = fdopen( fd, "rb" )) == NULL ) { return 0; } /* read & discard the file signature */ if ( fread( &header, sizeof( mpdat_hdr), 1, mp ) != 1 ) { return 0; } if ( memcmp( header.signature, "WMMP", 4 ) != 0 ) { return 0; } fprintf( stderr, "track %d, size %x, time %ums, frames %d\n", trackno, GUINT32_FROM_BE( header.size ), GUINT32_FROM_BE( header.time ), GUINT32_FROM_BE( header.frames )); statbuf.st_size -= sizeof( mpdat_hdr ); while( statbuf.st_size ) { size_t size = fread( buffer, 1, BUFSIZ, mp ); if ( size <= 0 ) { break; } for ( i = 0; i < size; i++ ) { guint16 in = buffer[i]; fprintf( out, "%c", conv[in]); } statbuf.st_size -= size; } return 0; } /* aux data access */ static gboolean mple_nw_get_track_header( nw_track_t *track, mpdat_hdr *header ) { FILE *mpdat; gchar *mpdatname; mple_device *dev; gboolean retval = TRUE; nw_attr_t *path_attr = NULL; dev = (mple_device *)track->device; assert( dev != NULL ); path_attr = nw_get_attr((nw_device_t *)dev, "mountpoint" ); mpdatname = g_strdup_printf( dev->mptemplate, NW_STR_ATTR( path_attr ), track->tracknum ); if (( mpdat = fopen( mpdatname, "rb" )) != NULL ) { if ( fread( header, sizeof( mpdat_hdr ), 1, mpdat ) != 1 ) { dprintf( stderr, " error reading header\n" ); retval = FALSE; } fclose( mpdat ); } g_free( mpdatname ); return retval; } guint32 mple_track_size( nw_track_t *track ) { mpdat_hdr header; if ( mple_nw_get_track_header( track, &header )) { track->size = GUINT32_FROM_BE( header.size ); return track->size; } else { return 0; } } guint32 mple_track_time( nw_track_t *track ) { mpdat_hdr header; if ( mple_nw_get_track_header( track, &header )) { track->time = GUINT32_FROM_BE( header.time ); return track->time; } else { return 0; } } guint32 mple_track_frames( nw_track_t *track ) { mpdat_hdr header; if ( mple_nw_get_track_header( track, &header )) { track->frames = GUINT32_FROM_BE( header.frames ); return track->frames; } else { return 0; } } /* * functions to provide support for filesystem-style access */ #ifdef DEBUG guint32 _mple_filesize( nw_track_t *tptr ) { return id3_tag_render( tptr->id3tag, NULL ) + tptr->device->get_track_size( tptr ) - sizeof( mpdat_hdr ); } #else #define _mple_filesize(tptr) \ (id3_tag_render( tptr->id3tag, NULL ) + tptr->device->get_track_size( tptr ) - sizeof( mpdat_hdr )) #endif /* * create a filehandle. Should have similar semantics to fopen, once I * finish it out... */ NWFILE *mple_open( nw_device_t *dev, guint32 folder, guint32 tracknum, const gchar *filename, char *mode ) { MPLEFILE *mfp = NULL; nw_track_t *tptr = NULL; gchar *datname = NULL; gchar *dirname = NULL; mple_device *device = (mple_device *)dev; nw_track_t *nw_track = NULL; nw_attr_t *path_attr = NULL; assert( dev != NULL ); path_attr = nw_get_attr( dev, "mountpoint" ); if ( !(device->aux & MPLE_MSN)) { if ( !mple_get_media_serial_number( dev )) { goto out; } } tptr = nw_get_track_ptr( dev, tracknum ); /* * valid modes: * "r" "r+" - file must exist * "w", "w+" - file created if it doesn't exist * "a", "a+" - file created if it doesn't exist */ if ( mode[0] == 'r' && tptr == NULL ) { errno = ENOENT; goto out; } /* read/append: try and pick up any existing file data */ if ( tptr != NULL ) { if ( !mple_get_track_header( dev, tracknum )) { /* XXX */ goto out; } } if ( tptr == NULL && mode[0] == 'w' ) { /* XXX also cater for append! */ nw_track = g_new0( nw_track_t, 1 ); nw_track->device = dev; if ( filename == NULL || strlen( filename ) == 0 ) { errno = EINVAL; goto out; } if ( folder == 0 ) { /* hmm. this logic should maybe be further up */ if ( dev->folderlist == NULL ) { if ( dev->autosync ) { if ( dev->parse_directory( dev ) == NULL ) { if ( errno == 0 ) { errno = ENOENT; /* rrrr. */ } goto out; } } else { errno = ENOENT; goto out; } } folder = 1; } nw_track->filename = g_strdup( g_basename( filename )); if ( nw_add_to_folder( dev, folder, nw_track, tracknum, 0 ) == 0 ) { dprintf( stderr, "adding to folder: %s\n", strerror( errno )); goto out; } tptr = nw_get_track_ptr( dev, tracknum ); } mfp = g_new0( MPLEFILE, 1 ); mfp->nwfile.tptr = tptr; mfp->nwfile.ptr = 0; mfp->nwfile.buffer = NULL; mfp->nwfile.bufsize = 0; mple_build_conv_array( tptr->tracknum, mfp->conv, device->msn & 0xff ); if ( mode[0] != 'r' ) { mfp->nwfile.writing = TRUE; } if ( mfp->nwfile.writing ) { mfp->nwfile.mp3f = g_new0( mp3file, 1 ); mfp->nwfile.mp3f->fp = NULL; mfp->nwfile.mp3f->fsizeold = 0; mfp->nwfile.mp3f->bsbuf = mfp->nwfile.mp3f->bsspace[1]; mfp->nwfile.mp3f->bsnum = 0; } datname = g_strdup_printf( device->mptemplate, NW_STR_ATTR( path_attr ), tptr->tracknum ); dirname = g_dirname( datname ); if ( mkpath( dirname, 0755 ) != TRUE ) { g_free( dirname ); goto out; } g_free( dirname ); mfp->nwfile.fp = fopen( datname, mode ); g_free( datname ); if ( mfp->nwfile.fp == NULL ) { if ( mfp->nwfile.writing == TRUE ) { g_free( tptr ); } g_free( mfp ); mfp = NULL; } else { mpdat_hdr dathdr; int terrno, i; memset( &dathdr, 0, sizeof( mpdat_hdr )); memcpy( dathdr.signature, "WMMP", 4 ); dathdr.msn = GUINT32_TO_BE(device->msn); memcpy( dathdr.magic, MPDAT_MAGIC, sizeof( dathdr.magic )); if ( mfp->nwfile.writing == TRUE ) { /* xxx this isn't entirely appropriate for appending */ if ( fwrite( &dathdr, sizeof( mpdat_hdr ), 1, mfp->nwfile.fp ) != 1 ) { dprintf( stderr, "fwrite: %s\n", strerror( errno )); terrno = errno; fclose( mfp->nwfile.fp ); g_free( tptr ); g_free( mfp ); mfp = NULL; errno = terrno; goto out; } } for ( i = 0; i < 15; i++ ) { mfp->nwfile.framerates[i] = 0; } mfp->nwfile.frames = 0; } out: return (NWFILE *)mfp; } int mple_seek( NWFILE *nfp, long offset, int whence ) { int newptr = 0; int retval = 0; MPLEFILE *mfp = (MPLEFILE *)nfp; if ( mfp == NULL ) { errno = EBADF; return -1; } switch( whence ) { case SEEK_SET: newptr = offset; break; case SEEK_CUR: newptr += offset; break; case SEEK_END: newptr = _mple_filesize( mfp->nwfile.tptr ) - offset; break; default: errno = EINVAL; return -1; } if ( newptr < 0 ) { errno = EINVAL; return -1; } /* fixme: if newptr won't fit in an off_t, return -1/EOVERFLOW */ if ( newptr > id3_tag_render( mfp->nwfile.tptr->id3tag, NULL )) { retval = fseek( mfp->nwfile.fp, newptr - id3_tag_render( mfp->nwfile.tptr->id3tag, NULL ) + sizeof( mpdat_hdr ), SEEK_SET ); } else { retval = fseek( mfp->nwfile.fp, sizeof( mpdat_hdr ), SEEK_SET ); } if ( retval == 0 ) { mfp->nwfile.ptr = newptr; } return retval; } long mple_tell( NWFILE *nfp ) { MPLEFILE *mfp = (MPLEFILE *)nfp; if ( mfp == NULL ) { errno = EBADF; return -1; } return mfp->nwfile.ptr; } /* * fread-like function */ size_t mple_read( void *ptr, size_t size, size_t nmemb, NWFILE *nfp ) { size_t retval = -1, i, nbytes; gchar *myptr = ptr; MPLEFILE *mfp = (MPLEFILE *)nfp; signed long id3datalen = 0; nbytes = MIN(( size * nmemb ), _mple_filesize( mfp->nwfile.tptr ) - mfp->nwfile.ptr ); if ( mfp->nwfile.tptr->id3tag != NULL ) { id3datalen = id3_tag_render( mfp->nwfile.tptr->id3tag, NULL ); } if ( mfp->nwfile.ptr < id3datalen ) { gchar *id3data = g_malloc0( id3datalen ); id3datalen = id3_tag_render( mfp->nwfile.tptr->id3tag, (id3_byte_t *)id3data ); memcpy( myptr, &(id3data[mfp->nwfile.ptr]), MIN( id3datalen - mfp->nwfile.ptr, size * nmemb )); g_free( id3data ); retval = MIN( id3datalen - mfp->nwfile.ptr, size *nmemb ); myptr += retval; mfp->nwfile.ptr += retval; nbytes -= retval; } else { retval = 0; } if ( nbytes ) { nbytes = fread( myptr, 1, nbytes, mfp->nwfile.fp ); if ( nbytes >= 0 ) { retval += nbytes; mfp->nwfile.ptr += nbytes; /* apply conv */ for ( i = 0; i < nbytes; i++ ) { myptr[i] = mfp->conv[(guint8)myptr[i]]; } } else { goto out; } } out: return retval; } /* * fwrite() for MPLEFILE handles */ size_t mple_write( void *ptr, size_t size, size_t nmemb, NWFILE *nfp ) { guint32 i; guint8 *buffer = ptr; MPLEFILE *mfp = (MPLEFILE *)nfp; /* apply scrambling */ for ( i = 0; i < size * nmemb; i++ ) { buffer[i] = mfp->conv[buffer[i]]; } return fwrite( ptr, size, nmemb, mfp->nwfile.fp ); } /* * fclose() for MPLEFILE handles */ int mple_close( NWFILE *nfp ) { int retval = 0; mpdat_hdr dathdr; MPLEFILE *mfp = (MPLEFILE *)nfp; id3_utf8_t *title = NULL, *artist = NULL; if ( mfp != NULL ) { if ( mfp->nwfile.fp != NULL ) { if ( mfp->nwfile.writing == TRUE ) { double seconds = 0; int i; /* need this to figure out the filesize */ fseek( mfp->nwfile.fp, 0, SEEK_END ); /* now calculate the length */ for ( i = 0; i < 15; i++ ) { seconds += (double)(mfp->nwfile.flen * mfp->nwfile.framerates[i] ) / (double)(mfp->nwfile.frate * 125); } /* convert to miliseconds */ seconds *= 1000; memset( &dathdr, 0, sizeof( mpdat_hdr )); memcpy( dathdr.signature, "WMMP", 4 ); dathdr.size = GUINT32_FROM_BE( ftell( mfp->nwfile.fp )); dathdr.frames = GUINT32_FROM_BE( mfp->nwfile.frames ); dathdr.time = GUINT32_FROM_BE( seconds ); dathdr.msn = GUINT32_TO_BE(((mple_device *)mfp->nwfile.tptr->device)->msn ); memcpy( dathdr.magic, MPDAT_MAGIC, sizeof( MPDAT_MAGIC )); fflush( mfp->nwfile.fp ); rewind( mfp->nwfile.fp ); if ( fwrite( &dathdr, sizeof( mpdat_hdr ), 1, mfp->nwfile.fp ) != 1 ) { dprintf( stderr, "writing header: %s\n", strerror( errno )); } } if ( mfp->nwfile.bufsize != 0 ) { dprintf( stderr, " %ld bytes left!\n", mfp->nwfile.bufsize ); /* I don't bloody well care. your input is malformed, if there's anything left */ } if ( mfp->nwfile.buffer != NULL ) { g_free( mfp->nwfile.buffer ); } if ( mfp->nwfile.mp3f != NULL ) { g_free( mfp->nwfile.mp3f ); } retval = fclose( mfp->nwfile.fp ); if ( retval != 0 ) { dprintf( stderr, "closing file: %s\n", strerror( errno )); } mfp->nwfile.fp = NULL; } /* update the metadata */ if ( mfp->nwfile.tptr->filename != NULL ) { size_t convlen; guint16 *conv = gchar_to_utf16be( mfp->nwfile.tptr->filename, 127, &convlen ); memcpy( &((pblist_track *)mfp->nwfile.tptr->trackdata)->filename, conv, convlen ); g_free( conv ); nfp->tptr->device->dirty = TRUE; } artist = nw_get_tag( mfp->nwfile.tptr, ID3_FRAME_ARTIST ); if ( artist != NULL ) { size_t convlen; guint16 *conv = gchar_to_utf16be( artist, 127, &convlen ); memcpy( &((pblist_track *)mfp->nwfile.tptr->trackdata)->artist, conv, convlen ); g_free( conv ); nfp->tptr->device->dirty = TRUE; g_free( artist ); } title = nw_get_tag( mfp->nwfile.tptr, ID3_FRAME_TITLE ); if ( title != NULL ) { size_t convlen; guint16 *conv = utf8_to_utf16be( title, 127, &convlen ); memcpy( &((pblist_track *)mfp->nwfile.tptr->trackdata)->title, conv, convlen ); g_free( conv ); nfp->tptr->device->dirty = TRUE; g_free( title ); } /* free up the trackdata structure also */ if ( mfp->nwfile.tptr->filename != NULL ) { g_free( mfp->nwfile.tptr->filename ); mfp->nwfile.tptr->filename = NULL; } if ( mfp->nwfile.tptr->fullpath != NULL ) { g_free( mfp->nwfile.tptr->fullpath ); mfp->nwfile.tptr->fullpath = NULL; } if ( mfp->nwfile.tptr->id3tag != NULL ) { id3_tag_delete( mfp->nwfile.tptr->id3tag ); mfp->nwfile.tptr->id3tag = NULL; } g_free( mfp ); } return retval; } int mple_stat( nw_device_t *dev, guint32 tracknum, struct stat *buf ) { int retval = -1; nw_track_t *tptr = NULL; mple_device *device = (mple_device *)dev; gchar *datname = NULL; nw_attr_t *path_attr = NULL; dprintf( stderr, "%s: enter\n", __FUNCTION__ ); path_attr = nw_get_attr( dev, "mountpoint" ); if ( buf == NULL ) { errno = EINVAL; goto out; } if (( tptr = nw_get_track_ptr( dev, tracknum )) == NULL ) { goto out; } datname = g_strdup_printf( device->mptemplate, NW_STR_ATTR( path_attr ), tracknum ); retval = stat( datname, buf ); g_free( datname ); if ( retval == 0 ) { if ( mple_get_track_header( dev, tracknum )) { /* adjust for our nefarious purposes */ buf->st_size = _mple_filesize( tptr ); /* not sure about this, but it looks like there's a leak somewhere */ /* if ( tptr->id3data != NULL ) { g_free( tptr->id3data ); tptr->id3data = NULL; } */ } else { retval = -1; } } out: dprintf( stderr, "%s: exit %d\n", __FUNCTION__, retval ); return retval; } /* * pull the track header and the ID3 metadata for the specified track */ gboolean mple_get_track_header( nw_device_t *dev, guint16 tracknum ) { nw_track_t *tptr; gboolean retval = FALSE; struct id3_frame *frame = NULL; guint16 swapped[129], i; dprintf( stderr, "%s: enter\n", __FUNCTION__ ); tptr = nw_get_track_ptr( dev, tracknum ); if ( tptr == NULL ) { dprintf( stderr, "%s: no such track %d\n", __FUNCTION__, tracknum ); goto out; } if ( tptr->id3tag != NULL ) { id3_utf8_t *title = NULL; dprintf( stderr, "%s: one less reason to use id3data\n", __FUNCTION__ ); title = nw_get_tag( tptr, ID3_FRAME_TITLE ); if ( title == NULL ) { dprintf( stderr, "no tag set, though\n" ); } else { g_free( title ); } } else { /* now populate the ID3 structure */ id3_ucs4_t *title = NULL; id3_ucs4_t *artist = NULL; dprintf( stderr, "%s: creating another id3tag\n", __FUNCTION__ ); tptr->id3tag = id3_tag_new(); frame = id3_frame_new( ID3_FRAME_TITLE ); id3_field_settextencoding( &frame->fields[0], ID3_FIELD_TEXTENCODING_ISO_8859_1 ); /* annoyingly, the id3 library can't handle UTF16BE. On the other hand, this allows me to make sure that the string's null-terminated. */ memset( swapped, 0, 129 ); for ( i = 0; i < 128 && ((pblist_track *)tptr->trackdata)->title[i]; i++ ) { swapped[i] = GUINT16_FROM_BE(((pblist_track *)tptr->trackdata)->title[i] ); } title = id3_utf16_ucs4duplicate( swapped ); id3_field_addstring( &frame->fields[1], title ); id3_tag_attachframe( tptr->id3tag, frame ); id3_frame_delete( frame ); frame = id3_frame_new( ID3_FRAME_ARTIST ); id3_field_settextencoding(&frame->fields[0], ID3_FIELD_TEXTENCODING_UTF_8 ); memset( swapped, 0, 129 ); for ( i = 0; i < 128 && ((pblist_track *)tptr->trackdata)->artist[i]; i++ ) { swapped[i] = GUINT16_FROM_BE(((pblist_track *)tptr->trackdata)->artist[i] ); } artist = id3_utf16_ucs4duplicate( swapped ); id3_field_addstring( &frame->fields[1], artist ); id3_tag_attachframe( tptr->id3tag, frame ); id3_frame_delete( frame ); if ( title != NULL ) { g_free( title ); } if ( artist != NULL ) { g_free( artist ); } } retval = TRUE; out: return retval; } /* * helper functions to convert track/album data to and from utf8 */ /* * render a UTF16BE string to UTF8 */ id3_utf8_t *utf8_from_utf16be( char *inbuffer, size_t maxlen ) { iconv_t cd = (iconv_t) -1; size_t outbytesleft, inbytesleft; size_t status; id3_utf8_t *outbuffer = NULL; char *outbuf; char *inbuf = inbuffer; /* Find out how many bytes to convert. Inbound data will be an array of 16bit values, either null-terminated or running right up against maxlen. */ for ( inbytesleft = 0; inbytesleft < maxlen; inbytesleft++ ) { if ( inbuf[inbytesleft * 2 + 1] == 0 && inbuf[inbytesleft * 2] == 0 ) { break; } } /* insane, but possible: UTF8 can use up to six bytes to represent a character. The +1 is for a trailing NULL */ outbytesleft = inbytesleft * 6 + 1; outbuffer = g_malloc( outbytesleft ); memset( outbuffer, 0, outbytesleft ); outbuf = (gchar *)outbuffer; /* convert 16bit size to 8bit size */ inbytesleft *= 2; cd = iconv_open( "UTF8", "UTF16BE" ); if ( cd != (iconv_t)(-1)) { status = iconv( cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft ); if ( status != -1 ) { goto out; } } /* if we get here, some part of the conversion failed */ dprintf( stderr, "conv: %s\n", strerror( errno )); out: if ( cd != (iconv_t) -1 ) { iconv_close( cd ); } return outbuffer; } /* and back again */ guint16 *utf8_to_utf16be( const id3_utf8_t *inbuffer, size_t maxlen, size_t *convlen ) { iconv_t cd = (iconv_t) -1; size_t outbytesleft, inbytesleft; size_t status, count; guint16 *outbuffer = NULL; char *outbuf; char *inbuf = (char *)inbuffer; inbytesleft = strlen( inbuf ); outbytesleft = 2 * maxlen; outbuffer = g_malloc( outbytesleft ); memset( outbuffer, 0, outbytesleft ); outbuf = (char *)outbuffer; count = outbytesleft; cd = iconv_open( "UTF16BE", "UTF8" ); if ( cd != (iconv_t)(-1)) { status = iconv( cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft ); if ( convlen != NULL ) { *convlen = count - outbytesleft; } if ( status != -1 ) { goto out; } } /* if we get here, some part of the conversion failed */ dprintf( stderr, "conv: %s\n", strerror( errno )); out: if ( cd != (iconv_t) -1 ) { iconv_close( cd ); } return outbuffer; } /* * I don't really recommend using these functions; they're fairly * blunt. However, I can't make head nor tail of the documentation on * wide characters, multibyte characters, and so on, which means that * I'm using these until I figure out what I *should* be doing. */ /* convert guint16 character array */ gchar *mple_getstr( guint16 *input ) { wchar_t *wchar = guint16o_wchar( input ); gchar *output = NULL; output = g_malloc0( sizeof( char ) * wcslen( wchar ) + 1 ); if (( output != NULL ) && ( wchar != NULL )) { sprintf( output, "%ls", wchar ); g_free( wchar ); } return output; } /* bluntly convert a guint16 'character' array to a wchar_t character array. This is clamped to an upper limit of 128 chars, because that's as big as they get in this code */ wchar_t *guint16o_wchar( guint16 *input ) { wchar_t *output; size_t i; output = g_malloc0( sizeof( wchar_t ) * 129 ); for ( i = 0; i < 128; i++ ) { output[i] = GUINT16_FROM_BE( input[i] ); output[i + 1] = L'\0'; if ( input[i] == 0 ) { break; } } return output; }