Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 1 | /*************************************************************************** |
| 2 | * __________ __ ___. |
| 3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| 4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| 5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| 6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| 7 | * \/ \/ \/ \/ \/ |
| 8 | * $Id$ |
| 9 | * |
Dave Chapman | e332f4c | 2007-02-05 01:20:20 +0000 | [diff] [blame] | 10 | * Copyright (C) 2006-2007 Dave Chapman |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 11 | * |
Daniel Stenberg | 2acc0ac | 2008-06-28 18:10:04 +0000 | [diff] [blame] | 12 | * This program is free software; you can redistribute it and/or |
| 13 | * modify it under the terms of the GNU General Public License |
| 14 | * as published by the Free Software Foundation; either version 2 |
| 15 | * of the License, or (at your option) any later version. |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 16 | * |
| 17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| 18 | * KIND, either express or implied. |
| 19 | * |
| 20 | ****************************************************************************/ |
| 21 | |
Dominik Riebeling | c8d13b6 | 2010-01-30 17:02:37 +0000 | [diff] [blame] | 22 | |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 23 | #include <stdio.h> |
| 24 | #include <unistd.h> |
| 25 | #include <fcntl.h> |
| 26 | #include <string.h> |
| 27 | #include <stdlib.h> |
| 28 | #include <sys/types.h> |
| 29 | #include <sys/stat.h> |
Dave Chapman | 8280c8c | 2006-12-13 20:26:44 +0000 | [diff] [blame] | 30 | #include <sys/ioctl.h> |
Dominik Riebeling | 194b2ca | 2008-05-04 11:59:04 +0000 | [diff] [blame] | 31 | #include <errno.h> |
Dave Chapman | 8280c8c | 2006-12-13 20:26:44 +0000 | [diff] [blame] | 32 | |
Dave Chapman | 56780e3 | 2007-06-16 22:32:57 +0000 | [diff] [blame] | 33 | #include "ipodio.h" |
| 34 | |
Dave Chapman | 8280c8c | 2006-12-13 20:26:44 +0000 | [diff] [blame] | 35 | #if defined(linux) || defined (__linux) |
Dave Chapman | 49e016c | 2006-12-15 00:09:48 +0000 | [diff] [blame] | 36 | #include <sys/mount.h> |
Dave Chapman | 56780e3 | 2007-06-16 22:32:57 +0000 | [diff] [blame] | 37 | #include <linux/hdreg.h> |
Dave Chapman | 1eca02d | 2009-08-04 20:32:30 +0000 | [diff] [blame] | 38 | #include <scsi/scsi_ioctl.h> |
| 39 | #include <scsi/sg.h> |
| 40 | |
Dave Chapman | 49e016c | 2006-12-15 00:09:48 +0000 | [diff] [blame] | 41 | #define IPOD_SECTORSIZE_IOCTL BLKSSZGET |
Dave Chapman | 56780e3 | 2007-06-16 22:32:57 +0000 | [diff] [blame] | 42 | |
| 43 | static void get_geometry(struct ipod_t* ipod) |
| 44 | { |
| 45 | struct hd_geometry geometry; |
| 46 | |
| 47 | if (!ioctl(ipod->dh, HDIO_GETGEO, &geometry)) { |
| 48 | /* never use geometry.cylinders - it is truncated */ |
| 49 | ipod->num_heads = geometry.heads; |
| 50 | ipod->sectors_per_track = geometry.sectors; |
| 51 | } else { |
| 52 | ipod->num_heads = 0; |
| 53 | ipod->sectors_per_track = 0; |
| 54 | } |
| 55 | } |
| 56 | |
Dominik Riebeling | c8d13b6 | 2010-01-30 17:02:37 +0000 | [diff] [blame] | 57 | /* Linux SCSI Inquiry code based on the documentation and example code from |
Dave Chapman | 1eca02d | 2009-08-04 20:32:30 +0000 | [diff] [blame] | 58 | http://www.ibm.com/developerworks/linux/library/l-scsi-api/index.html |
| 59 | */ |
| 60 | |
| 61 | int ipod_scsi_inquiry(struct ipod_t* ipod, int page_code, |
| 62 | unsigned char* buf, int bufsize) |
| 63 | { |
| 64 | unsigned char cdb[6]; |
| 65 | struct sg_io_hdr hdr; |
| 66 | unsigned char sense_buffer[255]; |
| 67 | |
| 68 | memset(&hdr, 0, sizeof(hdr)); |
| 69 | |
| 70 | hdr.interface_id = 'S'; /* this is the only choice we have! */ |
| 71 | hdr.flags = SG_FLAG_LUN_INHIBIT; /* this would put the LUN to 2nd byte of cdb*/ |
| 72 | |
| 73 | /* Set xfer data */ |
| 74 | hdr.dxferp = buf; |
| 75 | hdr.dxfer_len = bufsize; |
| 76 | |
| 77 | /* Set sense data */ |
| 78 | hdr.sbp = sense_buffer; |
| 79 | hdr.mx_sb_len = sizeof(sense_buffer); |
| 80 | |
| 81 | /* Set the cdb format */ |
| 82 | cdb[0] = 0x12; |
| 83 | cdb[1] = 1; /* Enable Vital Product Data (EVPD) */ |
| 84 | cdb[2] = page_code & 0xff; |
| 85 | cdb[3] = 0; |
| 86 | cdb[4] = 0xff; |
| 87 | cdb[5] = 0; /* For control filed, just use 0 */ |
Dominik Riebeling | c8d13b6 | 2010-01-30 17:02:37 +0000 | [diff] [blame] | 88 | |
Dave Chapman | 1eca02d | 2009-08-04 20:32:30 +0000 | [diff] [blame] | 89 | hdr.dxfer_direction = SG_DXFER_FROM_DEV; |
| 90 | hdr.cmdp = cdb; |
| 91 | hdr.cmd_len = 6; |
| 92 | |
| 93 | int ret = ioctl(ipod->dh, SG_IO, &hdr); |
| 94 | |
| 95 | if (ret < 0) { |
| 96 | return -1; |
| 97 | } else { |
| 98 | return 0; |
| 99 | } |
| 100 | } |
| 101 | |
Dave Chapman | 8280c8c | 2006-12-13 20:26:44 +0000 | [diff] [blame] | 102 | #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) \ |
| 103 | || defined(__bsdi__) || defined(__DragonFly__) |
Dave Chapman | 49e016c | 2006-12-15 00:09:48 +0000 | [diff] [blame] | 104 | #include <sys/disk.h> |
| 105 | #define IPOD_SECTORSIZE_IOCTL DIOCGSECTORSIZE |
Dave Chapman | 56780e3 | 2007-06-16 22:32:57 +0000 | [diff] [blame] | 106 | |
| 107 | /* TODO: Implement this function for BSD */ |
| 108 | static void get_geometry(struct ipod_t* ipod) |
| 109 | { |
| 110 | /* Are these universal for all ipods? */ |
| 111 | ipod->num_heads = 255; |
| 112 | ipod->sectors_per_track = 63; |
| 113 | } |
| 114 | |
Dave Chapman | 1eca02d | 2009-08-04 20:32:30 +0000 | [diff] [blame] | 115 | int ipod_scsi_inquiry(struct ipod_t* ipod, int page_code, |
| 116 | unsigned char* buf, int bufsize) |
| 117 | { |
| 118 | /* TODO: Implement for BSD */ |
Dominik Riebeling | 93eefba | 2009-11-29 19:20:38 +0000 | [diff] [blame] | 119 | (void)ipod; |
| 120 | (void)page_code; |
| 121 | (void)buf; |
| 122 | (void)bufsize; |
Dave Chapman | 1eca02d | 2009-08-04 20:32:30 +0000 | [diff] [blame] | 123 | return -1; |
| 124 | } |
| 125 | |
Dave Chapman | 8280c8c | 2006-12-13 20:26:44 +0000 | [diff] [blame] | 126 | #elif defined(__APPLE__) && defined(__MACH__) |
Dominik Riebeling | c8d13b6 | 2010-01-30 17:02:37 +0000 | [diff] [blame] | 127 | /* OS X IOKit includes don't like VERSION being defined! */ |
| 128 | #undef VERSION |
Dave Chapman | 49e016c | 2006-12-15 00:09:48 +0000 | [diff] [blame] | 129 | #include <sys/disk.h> |
Dominik Riebeling | c8d13b6 | 2010-01-30 17:02:37 +0000 | [diff] [blame] | 130 | #include <CoreFoundation/CoreFoundation.h> |
| 131 | #include <IOKit/IOKitLib.h> |
| 132 | #include <IOKit/scsi-commands/SCSITaskLib.h> |
| 133 | #include <IOKit/scsi-commands/SCSICommandOperationCodes.h> |
Dave Chapman | 49e016c | 2006-12-15 00:09:48 +0000 | [diff] [blame] | 134 | #define IPOD_SECTORSIZE_IOCTL DKIOCGETBLOCKSIZE |
Dave Chapman | 56780e3 | 2007-06-16 22:32:57 +0000 | [diff] [blame] | 135 | |
| 136 | /* TODO: Implement this function for Mac OS X */ |
| 137 | static void get_geometry(struct ipod_t* ipod) |
| 138 | { |
| 139 | /* Are these universal for all ipods? */ |
| 140 | ipod->num_heads = 255; |
| 141 | ipod->sectors_per_track = 63; |
| 142 | } |
| 143 | |
Dave Chapman | 1eca02d | 2009-08-04 20:32:30 +0000 | [diff] [blame] | 144 | int ipod_scsi_inquiry(struct ipod_t* ipod, int page_code, |
| 145 | unsigned char* buf, int bufsize) |
| 146 | { |
Dominik Riebeling | c8d13b6 | 2010-01-30 17:02:37 +0000 | [diff] [blame] | 147 | /* OS X doesn't allow to simply send out a SCSI inquiry request but |
| 148 | * requires registering an interface handler first. |
| 149 | * Currently this is done on each inquiry request which is somewhat |
| 150 | * inefficient but the current ipodpatcher API doesn't really fit here. |
| 151 | * Based on the documentation in Apple's document |
| 152 | * "SCSI Architecture Model Device Interface Guide". |
| 153 | * |
| 154 | * WARNING: this code currently doesn't take the selected device into |
| 155 | * account. It simply looks for an Ipod on the system and uses |
| 156 | * the first match. |
| 157 | */ |
Dominik Riebeling | 93eefba | 2009-11-29 19:20:38 +0000 | [diff] [blame] | 158 | (void)ipod; |
Dominik Riebeling | c8d13b6 | 2010-01-30 17:02:37 +0000 | [diff] [blame] | 159 | int result = 0; |
| 160 | /* first, create a dictionary to match the device. This is needed to get the |
| 161 | * service. */ |
| 162 | CFMutableDictionaryRef match_dict; |
| 163 | match_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL); |
| 164 | if(match_dict == NULL) |
| 165 | return -1; |
| 166 | |
| 167 | /* set value to match. In case of the Ipod this is "iPodUserClientDevice". */ |
| 168 | CFMutableDictionaryRef sub_dict; |
| 169 | sub_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL); |
| 170 | CFDictionarySetValue(sub_dict, CFSTR(kIOPropertySCSITaskDeviceCategory), |
| 171 | CFSTR("iPodUserClientDevice")); |
| 172 | CFDictionarySetValue(match_dict, CFSTR(kIOPropertyMatchKey), sub_dict); |
| 173 | |
| 174 | if(sub_dict == NULL) |
| 175 | return -1; |
| 176 | |
| 177 | /* get an iterator for searching for the service. */ |
| 178 | kern_return_t kr; |
| 179 | io_iterator_t iterator = IO_OBJECT_NULL; |
| 180 | /* get matching services from IO registry. Consumes one reference to |
| 181 | * the dictionary, so no need to release that. */ |
| 182 | kr = IOServiceGetMatchingServices(kIOMasterPortDefault, match_dict, &iterator); |
| 183 | |
| 184 | if(!iterator | (kr != kIOReturnSuccess)) |
| 185 | return -1; |
| 186 | |
| 187 | /* get interface and obtain exclusive access */ |
| 188 | SInt32 score; |
| 189 | HRESULT herr; |
| 190 | kern_return_t err; |
| 191 | IOCFPlugInInterface **plugin_interface = NULL; |
| 192 | SCSITaskDeviceInterface **interface = NULL; |
| 193 | io_service_t device = IO_OBJECT_NULL; |
| 194 | device = IOIteratorNext(iterator); |
| 195 | |
| 196 | err = IOCreatePlugInInterfaceForService(device, kIOSCSITaskDeviceUserClientTypeID, |
| 197 | kIOCFPlugInInterfaceID, &plugin_interface, |
| 198 | &score); |
| 199 | |
| 200 | if(err != noErr) { |
| 201 | return -1; |
| 202 | } |
| 203 | /* query the plugin interface for task interface */ |
| 204 | herr = (*plugin_interface)->QueryInterface(plugin_interface, |
| 205 | CFUUIDGetUUIDBytes(kIOSCSITaskDeviceInterfaceID), (LPVOID*)&interface); |
| 206 | if(herr != S_OK) { |
| 207 | IODestroyPlugInInterface(plugin_interface); |
| 208 | return -1; |
| 209 | } |
| 210 | |
| 211 | err = (*interface)->ObtainExclusiveAccess(interface); |
| 212 | if(err != noErr) { |
| 213 | (*interface)->Release(interface); |
| 214 | IODestroyPlugInInterface(plugin_interface); |
| 215 | return -1; |
| 216 | } |
| 217 | |
| 218 | /* do the inquiry */ |
| 219 | SCSITaskInterface **task = NULL; |
| 220 | |
| 221 | task = (*interface)->CreateSCSITask(interface); |
| 222 | if(task != NULL) { |
| 223 | kern_return_t err; |
| 224 | SCSITaskStatus task_status; |
| 225 | IOVirtualRange* range; |
| 226 | SCSI_Sense_Data sense_data; |
| 227 | SCSICommandDescriptorBlock cdb; |
| 228 | UInt64 transfer_count = 0; |
| 229 | memset(buf, 0, bufsize); |
| 230 | /* allocate virtual range for buffer. */ |
| 231 | range = (IOVirtualRange*) malloc(sizeof(IOVirtualRange)); |
| 232 | memset(&sense_data, 0, sizeof(sense_data)); |
| 233 | memset(cdb, 0, sizeof(cdb)); |
| 234 | /* set up range. address is buffer address, length is request size. */ |
| 235 | range->address = (IOVirtualAddress)buf; |
| 236 | range->length = bufsize; |
| 237 | /* setup CDB */ |
| 238 | cdb[0] = 0x12; /* inquiry */ |
| 239 | cdb[1] = 1; |
| 240 | cdb[2] = page_code; |
| 241 | cdb[4] = bufsize; |
| 242 | |
| 243 | /* set cdb in task */ |
| 244 | err = (*task)->SetCommandDescriptorBlock(task, cdb, kSCSICDBSize_6Byte); |
| 245 | if(err != kIOReturnSuccess) { |
| 246 | result = -1; |
| 247 | goto cleanup; |
| 248 | } |
| 249 | err = (*task)->SetScatterGatherEntries(task, range, 1, bufsize, |
| 250 | kSCSIDataTransfer_FromTargetToInitiator); |
| 251 | if(err != kIOReturnSuccess) { |
| 252 | result = -1; |
| 253 | goto cleanup; |
| 254 | } |
| 255 | /* set timeout */ |
| 256 | err = (*task)->SetTimeoutDuration(task, 10000); |
| 257 | if(err != kIOReturnSuccess) { |
| 258 | result = -1; |
| 259 | goto cleanup; |
| 260 | } |
| 261 | |
| 262 | /* request data */ |
| 263 | err = (*task)->ExecuteTaskSync(task, &sense_data, &task_status, &transfer_count); |
| 264 | if(err != kIOReturnSuccess) { |
| 265 | result = -1; |
| 266 | goto cleanup; |
| 267 | } |
| 268 | /* cleanup */ |
| 269 | free(range); |
| 270 | |
| 271 | /* release task interface */ |
| 272 | (*task)->Release(task); |
| 273 | } |
| 274 | else { |
| 275 | result = -1; |
| 276 | } |
| 277 | cleanup: |
| 278 | /* cleanup interface */ |
| 279 | (*interface)->ReleaseExclusiveAccess(interface); |
| 280 | (*interface)->Release(interface); |
| 281 | IODestroyPlugInInterface(plugin_interface); |
| 282 | |
| 283 | return result; |
Dave Chapman | 1eca02d | 2009-08-04 20:32:30 +0000 | [diff] [blame] | 284 | } |
| 285 | |
Dave Chapman | 8280c8c | 2006-12-13 20:26:44 +0000 | [diff] [blame] | 286 | #else |
| 287 | #error No sector-size detection implemented for this platform |
| 288 | #endif |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 289 | |
Dave Chapman | f225f04 | 2007-09-01 22:55:37 +0000 | [diff] [blame] | 290 | #if defined(__APPLE__) && defined(__MACH__) |
| 291 | static int ipod_unmount(struct ipod_t* ipod) |
| 292 | { |
| 293 | char cmd[4096]; |
| 294 | int res; |
| 295 | |
| 296 | sprintf(cmd, "/usr/sbin/diskutil unmount \"%ss2\"",ipod->diskname); |
| 297 | fprintf(stderr,"[INFO] "); |
| 298 | res = system(cmd); |
| 299 | |
| 300 | if (res==0) { |
| 301 | return 0; |
| 302 | } else { |
| 303 | perror("Unmount failed"); |
| 304 | return -1; |
| 305 | } |
| 306 | } |
| 307 | #endif |
| 308 | |
Dominik Riebeling | 3e489c1 | 2009-11-08 13:38:10 +0000 | [diff] [blame] | 309 | void ipod_print_error(char* msg) |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 310 | { |
| 311 | perror(msg); |
| 312 | } |
| 313 | |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 314 | int ipod_open(struct ipod_t* ipod, int silent) |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 315 | { |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 316 | ipod->dh=open(ipod->diskname,O_RDONLY); |
| 317 | if (ipod->dh < 0) { |
| 318 | if (!silent) perror(ipod->diskname); |
Dominik Riebeling | 194b2ca | 2008-05-04 11:59:04 +0000 | [diff] [blame] | 319 | if(errno == EACCES) return -2; |
| 320 | else return -1; |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 321 | } |
| 322 | |
Dave Chapman | 56780e3 | 2007-06-16 22:32:57 +0000 | [diff] [blame] | 323 | /* Read information about the disk */ |
| 324 | |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 325 | if(ioctl(ipod->dh,IPOD_SECTORSIZE_IOCTL,&ipod->sector_size) < 0) { |
Dave Chapman | 4280640 | 2010-01-28 09:37:05 +0000 | [diff] [blame] | 326 | ipod->sector_size=512; |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 327 | if (!silent) { |
| 328 | fprintf(stderr,"[ERR] ioctl() call to get sector size failed, defaulting to %d\n" |
| 329 | ,ipod->sector_size); |
Dominik Riebeling | c8d13b6 | 2010-01-30 17:02:37 +0000 | [diff] [blame] | 330 | } |
Dave Chapman | 8280c8c | 2006-12-13 20:26:44 +0000 | [diff] [blame] | 331 | } |
Dave Chapman | 56780e3 | 2007-06-16 22:32:57 +0000 | [diff] [blame] | 332 | |
| 333 | get_geometry(ipod); |
| 334 | |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 335 | return 0; |
| 336 | } |
| 337 | |
| 338 | |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 339 | int ipod_reopen_rw(struct ipod_t* ipod) |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 340 | { |
Dave Chapman | f225f04 | 2007-09-01 22:55:37 +0000 | [diff] [blame] | 341 | #if defined(__APPLE__) && defined(__MACH__) |
| 342 | if (ipod_unmount(ipod) < 0) |
| 343 | return -1; |
| 344 | #endif |
| 345 | |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 346 | close(ipod->dh); |
| 347 | ipod->dh=open(ipod->diskname,O_RDWR); |
| 348 | if (ipod->dh < 0) { |
| 349 | perror(ipod->diskname); |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 350 | return -1; |
| 351 | } |
| 352 | return 0; |
| 353 | } |
| 354 | |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 355 | int ipod_close(struct ipod_t* ipod) |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 356 | { |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 357 | close(ipod->dh); |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 358 | return 0; |
| 359 | } |
| 360 | |
| 361 | int ipod_alloc_buffer(unsigned char** sectorbuf, int bufsize) |
| 362 | { |
| 363 | *sectorbuf=malloc(bufsize); |
| 364 | if (*sectorbuf == NULL) { |
| 365 | return -1; |
| 366 | } |
| 367 | return 0; |
| 368 | } |
| 369 | |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 370 | int ipod_seek(struct ipod_t* ipod, unsigned long pos) |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 371 | { |
| 372 | off_t res; |
| 373 | |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 374 | res = lseek(ipod->dh, pos, SEEK_SET); |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 375 | |
| 376 | if (res == -1) { |
| 377 | return -1; |
| 378 | } |
| 379 | return 0; |
| 380 | } |
| 381 | |
Dave Chapman | 2cc80f5 | 2007-07-29 21:19:14 +0000 | [diff] [blame] | 382 | ssize_t ipod_read(struct ipod_t* ipod, unsigned char* buf, int nbytes) |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 383 | { |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 384 | return read(ipod->dh, buf, nbytes); |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 385 | } |
| 386 | |
Dave Chapman | 2cc80f5 | 2007-07-29 21:19:14 +0000 | [diff] [blame] | 387 | ssize_t ipod_write(struct ipod_t* ipod, unsigned char* buf, int nbytes) |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 388 | { |
Dave Chapman | 31aa452 | 2007-02-04 11:42:11 +0000 | [diff] [blame] | 389 | return write(ipod->dh, buf, nbytes); |
Dave Chapman | 4b7e1e0 | 2006-12-13 09:02:18 +0000 | [diff] [blame] | 390 | } |
Dave Chapman | 1eca02d | 2009-08-04 20:32:30 +0000 | [diff] [blame] | 391 | |