A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Amaury Pouly
11 *
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.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#define _XOPEN_SOURCE 500
22#define _DEFAULT_SOURCE
23#include <stdlib.h>
24#include <stdio.h>
25#include <stdarg.h>
26#include "rbscsi.h"
27
28static void misc_std_printf(void *user, const char *fmt, ...)
29{
30 (void) user;
31 va_list args;
32 va_start(args, fmt);
33 vprintf(fmt, args);
34 va_end(args);
35}
36
37#if defined(_WIN32) || defined(__WIN32__)
38#define RB_SCSI_WINDOWS
39#include <windows.h>
40#include <ntddscsi.h>
41#include <stdint.h>
42typedef HANDLE rb_scsi_handle_t;
43#elif defined(__linux) || defined(__linux__) || defined(linux)
44#include <unistd.h>
45#include <fcntl.h>
46#include <stdio.h>
47#include <string.h>
48#include <stdint.h>
49#include <errno.h>
50#include <sys/ioctl.h>
51#include <scsi/sg.h>
52#include <dirent.h>
53#include <linux/limits.h>
54#include <sys/types.h>
55#include <sys/stat.h>
56#define RB_SCSI_LINUX
57typedef int rb_scsi_handle_t;
58#else
59typedef void *rb_scsi_handle_t;
60#endif
61
62struct rb_scsi_device_t
63{
64 rb_scsi_handle_t handle;
65 void *user;
66 rb_scsi_printf_t printf;
67 unsigned flags;
68};
69
70/* Linux */
71#ifdef RB_SCSI_LINUX
72/* the values for hdr.driver_status are not defined in public headers */
73#define DRIVER_SENSE 0x08
74
75rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user,
76 rb_scsi_printf_t printf)
77{
78 if(printf == NULL)
79 printf = misc_std_printf;
80 int fd = open(path, (flags & RB_SCSI_READ_ONLY) ? O_RDONLY : O_RDWR);
81 if(fd < 0)
82 {
83 if(flags & RB_SCSI_DEBUG)
84 printf(user, "rb_scsi: open failed: %s\n", strerror(errno));
85 return NULL;
86 }
87 rb_scsi_device_t dev = malloc(sizeof(struct rb_scsi_device_t));
88 dev->flags = flags;
89 dev->handle = fd;
90 dev->user = user;
91 dev->printf = printf ? printf : misc_std_printf;
92 return dev;
93}
94
95int rb_scsi_raw_xfer(rb_scsi_device_t dev, struct rb_scsi_raw_cmd_t *raw)
96{
97#define rb_printf(...) \
98 do{ if(dev->flags & RB_SCSI_DEBUG) dev->printf(dev->user, __VA_ARGS__); }while(0)
99 sg_io_hdr_t hdr;
100 memset(&hdr, 0, sizeof(hdr));
101 /* prepare transfer descriptor */
102 hdr.interface_id = 'S';
103 switch(raw->dir)
104 {
105 case RB_SCSI_NONE: hdr.dxfer_direction = SG_DXFER_NONE; break;
106 case RB_SCSI_READ: hdr.dxfer_direction = SG_DXFER_FROM_DEV; break;
107 case RB_SCSI_WRITE: hdr.dxfer_direction = SG_DXFER_TO_DEV; break;
108 default: rb_printf("rb_scsi: invalid direction"); return -1;
109 }
110 hdr.cmd_len = raw->cdb_len;
111 hdr.mx_sb_len = raw->sense_len;
112 hdr.dxfer_len = raw->buf_len;
113 hdr.dxferp = raw->buf;
114 hdr.cmdp = raw->cdb;
115 hdr.sbp = raw->sense;
116 hdr.timeout = raw->tmo * 1000;
117 hdr.flags = SG_FLAG_LUN_INHIBIT;
118 /* do raw command */
119 if(ioctl(dev->handle, SG_IO, &hdr) < 0)
120 {
121 raw->status = errno;
122 rb_printf("rb_scsi: ioctl failed: %s\n", strerror(raw->status));
123 return RB_SCSI_OS_ERROR;
124 }
125 /* error handling is weird: host status has the priority */
126 if(hdr.host_status)
127 {
128 rb_printf("rb_scsi: host error: %d\n", hdr.host_status);
129 raw->status = hdr.host_status;
130 return RB_SCSI_ERROR;
131 }
132 raw->status = hdr.status & 0x7e;
133 raw->buf_len -= hdr.resid;
134 raw->sense_len = hdr.sb_len_wr;
135 /* driver error can report sense */
136 if((hdr.driver_status & 0xf) == DRIVER_SENSE)
137 {
138 rb_printf("rb_scsi: driver status reported sense\n");
139 return RB_SCSI_SENSE;
140 }
141 /* otherwise errors */
142 if(hdr.driver_status)
143 {
144 rb_printf("rb_scsi: driver error: %d\n", hdr.driver_status);
145 return RB_SCSI_ERROR;
146 }
147 /* scsi status can imply sense */
148 if(raw->status == RB_SCSI_CHECK_CONDITION || raw->status == RB_SCSI_COMMAND_TERMINATED)
149 return RB_SCSI_SENSE;
150 return raw->status ? RB_SCSI_STATUS : RB_SCSI_OK;
151#undef rb_printf
152}
153
154void rb_scsi_close(rb_scsi_device_t dev)
155{
156 close(dev->handle);
157 free(dev);
158}
159
160/* find the first entry of a directory (which is expended to contain only one) and turn
161 * it into a /dev/ path */
162static int scan_resolve_first_dev_path(char *path, size_t pathsz)
163{
164 DIR *dir = opendir(path);
165 if(dir == NULL)
166 return 0;
167 struct dirent *d;
168 int ret = 0;
169 while((d = readdir(dir)))
170 {
171 if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
172 continue;
173 /* we expect a directory, but it could be a symlink, only the name matters */
174 if(d->d_type == DT_LNK || d->d_type == DT_DIR)
175 {
176 snprintf(path, pathsz, "/dev/%s", d->d_name);
177 ret = 1;
178 }
179 break;
180 }
181 closedir(dir);
182 return ret;
183}
184
185static char *read_file_or_null(const char *path)
186{
187 FILE *f = fopen(path, "r");
188 if(f == NULL)
189 return NULL;
190 char buffer[1024];
191 if(fgets(buffer, sizeof(buffer), f) == NULL)
192 {
193 fclose(f);
194 return NULL;
195 }
196 fclose(f);
197 /* the kernel appends a '\n' at the end, remove it */
198 size_t len = strlen(buffer);
199 if(len > 0 && buffer[len - 1] == '\n')
200 buffer[len - 1] = 0;
201 return strdup(buffer);
202}
203
204struct rb_scsi_devent_t *rb_scsi_list(void)
205{
206 /* list devices in /sys/class/scsi_generic
207 * we only keep entries of the form sgX */
208#define SYS_SCSI_DEV_PATH "/sys/class/scsi_generic"
209 DIR *dir = opendir(SYS_SCSI_DEV_PATH);
210 if(dir == NULL)
211 return NULL;
212 struct dirent *d;
213 struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t));
214 dev[0].scsi_path = NULL;
215 dev[0].block_path = NULL;
216 int nr_dev = 0;
217 while((d = readdir(dir)))
218 {
219 if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
220 continue;
221 /* make sure the name is of the form sgX, and extract X */
222 int sg_idx;
223 if(sscanf(d->d_name, "sg%d", &sg_idx) != 1)
224 continue;
225 dev = realloc(dev, (2 + nr_dev) * sizeof(struct rb_scsi_devent_t));
226
227 char path[PATH_MAX];
228 snprintf(path, sizeof(path), "/dev/sg%d", sg_idx);
229 dev[nr_dev].scsi_path = strdup(path);
230 /* /sys/class/scsi_generic/sgX is a folder containing a subfolder 'device' that contains
231 * - a 'block' folder with a single entry: the block device
232 * - the vendor/model/rev files */
233 snprintf(path, sizeof(path), SYS_SCSI_DEV_PATH "/sg%d/device/block", sg_idx);
234 if(!scan_resolve_first_dev_path(path, sizeof(path)))
235 path[0] = 0;
236 dev[nr_dev].block_path = path[0] == 0 ? NULL : strdup(path);
237 /* fill vendor/model/rev */
238 snprintf(path, sizeof(path), SYS_SCSI_DEV_PATH "/sg%d/device/vendor", sg_idx);
239 dev[nr_dev].vendor = read_file_or_null(path);
240 snprintf(path, sizeof(path), SYS_SCSI_DEV_PATH "/sg%d/device/model", sg_idx);
241 dev[nr_dev].model = read_file_or_null(path);
242 snprintf(path, sizeof(path), SYS_SCSI_DEV_PATH "/sg%d/device/rev", sg_idx);
243 dev[nr_dev].rev = read_file_or_null(path);
244
245 /* sentinel */
246 dev[++nr_dev].scsi_path = NULL;
247 dev[nr_dev].block_path = NULL;
248 }
249 closedir(dir);
250 return dev;
251}
252/* Windows */
253#elif defined(RB_SCSI_WINDOWS)
254/* return either path or something allocated with malloc() */
255static const char *map_to_physical_drive(const char *path, unsigned flags, void *user,
256 rb_scsi_printf_t printf)
257{
258 /* don't do anything if path starts with '\' */
259 if(path[0] == '\\')
260 return path;
261 /* Convert to UNC path (C: -> \\.\C:) otherwise it won't work) */
262 char *unc_path = malloc(strlen(path) + 5);
263 sprintf(unc_path, "\\\\.\\%s", path);
264 if(flags & RB_SCSI_DEBUG)
265 printf(user, "rb_scsi: map to UNC path: %s\n", unc_path);
266 return unc_path;
267}
268
269rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user,
270 rb_scsi_printf_t printf)
271{
272 if(printf == NULL)
273 printf = misc_std_printf;
274 /* magic to auto-detect physical drive */
275 const char *open_path = map_to_physical_drive(path, flags, user, printf);
276 HANDLE h = CreateFileA(open_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
277 NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING, NULL);
278 /* free path if it was allocated */
279 if(open_path != path)
280 free((char *)open_path);
281 if(h == INVALID_HANDLE_VALUE)
282 {
283 if(flags & RB_SCSI_DEBUG)
284 {
285 LPSTR msg = NULL;
286 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
287 NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL);
288 printf(user, "rb_scsi: open failed: %s\n", msg);
289 LocalFree(msg);
290 }
291 return NULL;
292 }
293 rb_scsi_device_t dev = malloc(sizeof(struct rb_scsi_device_t));
294 dev->flags = flags;
295 dev->handle = h;
296 dev->user = user;
297 dev->printf = printf ? printf : misc_std_printf;
298 return dev;
299}
300
301int rb_scsi_raw_xfer(rb_scsi_device_t dev, struct rb_scsi_raw_cmd_t *raw)
302{
303#define rb_printf(...) \
304 do{ if(dev->flags & RB_SCSI_DEBUG) dev->printf(dev->user, __VA_ARGS__); }while(0)
305 /* we need to allocate memory for SCSI_PASS_THROUGH_WITH_BUFFERS and the
306 * data and sense */
307 DWORD length = sizeof(SCSI_PASS_THROUGH) + raw->buf_len + raw->sense_len;
308 DWORD returned = 0;
309 SCSI_PASS_THROUGH *spt = malloc(length);
310 memset(spt, 0, length);
311 spt->Length = sizeof(SCSI_PASS_THROUGH);
312 spt->PathId = 0;
313 spt->TargetId = 0;
314 spt->Lun = 0;
315 spt->CdbLength = raw->cdb_len;
316 memcpy(spt->Cdb, raw->cdb, raw->cdb_len);
317 spt->SenseInfoLength = raw->sense_len;
318 spt->SenseInfoOffset = spt->Length; /* Sense after header */
319 switch(raw->dir)
320 {
321 case RB_SCSI_NONE: spt->DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; break;
322 case RB_SCSI_READ: spt->DataIn = SCSI_IOCTL_DATA_IN; break;
323 case RB_SCSI_WRITE: spt->DataIn = SCSI_IOCTL_DATA_OUT; break;
324 default:
325 free(spt);
326 rb_printf("rb_scsi: invalid direction"); return -1;
327 }
328 spt->DataTransferLength = raw->buf_len;
329 spt->DataBufferOffset = spt->SenseInfoOffset + spt->SenseInfoLength; /* Data after Sense */
330 spt->TimeOutValue = raw->tmo;
331 /* on write, copy data to buffer */
332 if(raw->dir == RB_SCSI_WRITE)
333 memcpy((BYTE *)spt + spt->DataBufferOffset, raw->buf, raw->buf_len);
334 BOOL status = DeviceIoControl(dev->handle, IOCTL_SCSI_PASS_THROUGH,
335 spt, length, spt, length, &returned, FALSE);
336 if(!status)
337 {
338 if(dev->flags & RB_SCSI_DEBUG)
339 {
340 LPSTR msg = NULL;
341 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
342 NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL);
343 printf(dev->user, "rb_scsi: ioctl failed: %s\n", msg);
344 LocalFree(msg);
345 }
346 free(spt);
347 return RB_SCSI_OS_ERROR;
348 }
349 /* on read, copy data from buffer */
350 if(raw->dir == RB_SCSI_READ)
351 {
352 raw->buf_len = spt->DataTransferLength;
353 memcpy(raw->buf, (BYTE *)spt + spt->DataBufferOffset, raw->buf_len);
354 }
355 /* check status */
356 raw->status = spt->ScsiStatus;
357 if(raw->status == RB_SCSI_CHECK_CONDITION || raw->status == RB_SCSI_COMMAND_TERMINATED)
358 {
359 memcpy(raw->sense, (BYTE *)spt + spt->SenseInfoOffset, raw->sense_len);
360 free(spt);
361 return RB_SCSI_SENSE;
362 }
363 else
364 raw->sense_len = 0;
365 free(spt);
366 return raw->status ? RB_SCSI_STATUS : RB_SCSI_OK;
367#undef rb_printf
368}
369
370void rb_scsi_close(rb_scsi_device_t dev)
371{
372 CloseHandle(dev->handle);
373 free(dev);
374}
375
376struct rb_scsi_devent_t *rb_scsi_list(void)
377{
378 /* unimplemented */
379 struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t));
380 dev[0].scsi_path = NULL;
381 dev[0].block_path = NULL;
382 int nr_dev = 0;
383 /* list logical drives */
384 DWORD cbStrSize = GetLogicalDriveStringsA(0, NULL);
385 LPSTR pszDrives = malloc(cbStrSize);
386 cbStrSize = GetLogicalDriveStringsA(cbStrSize, pszDrives);
387 /* drives are separated by a NULL character, the last drive is just a NULL one */
388 for(LPSTR pszDriveRoot = pszDrives; *pszDriveRoot != '\0';
389 pszDriveRoot += lstrlen(pszDriveRoot) + 1)
390 {
391 // each drive is of the form "X:\"
392 char path[3];
393 path[0] = pszDriveRoot[0]; // letter
394 path[1] = ':';
395 path[2] = 0;
396 /* open drive */
397 rb_scsi_device_t rdev = rb_scsi_open(path, 0, NULL, NULL);
398 if(rdev == NULL)
399 continue; // ignore device
400 // send an INQUIRY device to check it's actually an SCSI device and get information
401 uint8_t inquiry_data[36];
402 uint8_t cdb[6] = {0x12, 0, 0, 0, sizeof(inquiry_data), 0}; // INQUIRY
403
404 struct rb_scsi_raw_cmd_t raw;
405 raw.dir = RB_SCSI_READ;
406 raw.cdb_len = sizeof(cdb);
407 raw.cdb = cdb;
408 raw.buf = inquiry_data;
409 raw.buf_len = sizeof(inquiry_data);
410 raw.sense_len = 0; // don't bother with SENSE, this command cannot possibly fail for good reasons
411 raw.sense = NULL;
412 raw.tmo = 5;
413 int ret = rb_scsi_raw_xfer(rdev, &raw);
414 rb_scsi_close(rdev);
415 if(ret != RB_SCSI_OK)
416 continue; // ignore device
417 if(raw.buf_len != (int)sizeof(inquiry_data))
418 continue; // ignore device (what kind of device would not return all the INQUIRY data?)
419
420 /* fill device details */
421 dev = realloc(dev, (2 + nr_dev) * sizeof(struct rb_scsi_devent_t));
422 dev[nr_dev].scsi_path = strdup(path);
423 dev[nr_dev].block_path = strdup(path);
424 /* fill vendor/model/rev */
425 char info[17];
426 snprintf(info, sizeof(info), "%.8s", inquiry_data + 8);
427 dev[nr_dev].vendor = strdup(info);
428 snprintf(info, sizeof(info), "%.16s", inquiry_data + 16);
429 dev[nr_dev].model = strdup(info);
430 snprintf(info, sizeof(info), "%.4s", inquiry_data + 32);
431 dev[nr_dev].rev = strdup(info);
432
433 /* sentinel */
434 dev[++nr_dev].scsi_path = NULL;
435 dev[nr_dev].block_path = NULL;
436 }
437
438 return dev;
439}
440/* other targets */
441#else
442rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user,
443 rb_scsi_printf_t printf)
444{
445 if(printf == NULL)
446 printf = misc_std_printf;
447 (void) path;
448 if(flags & RB_SCSI_DEBUG)
449 printf(user, "rb_scsi: unimplemented on this platform\n");
450 return NULL;
451}
452
453int rb_scsi_raw_xfer(rb_scsi_device_t dev, struct rb_scsi_raw_cmd_t *raw)
454{
455 return RB_SCSI_ERROR;
456}
457
458void rb_scsi_close(rb_scsi_device_t dev)
459{
460 free(dev);
461}
462
463struct rb_scsi_devent_t *rb_scsi_list(void)
464{
465 /* unimplemented */
466 struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t));
467 dev[0].scsi_path = NULL;
468 dev[0].block_path = NULL;
469 return dev;
470}
471#endif
472
473void rb_scsi_decode_sense(rb_scsi_device_t dev, void *_sense, int sense_len)
474{
475#define rb_printf(...) \
476 do{ if(dev->flags & RB_SCSI_DEBUG) dev->printf(dev->user, __VA_ARGS__); }while(0)
477
478 uint8_t *sense = _sense;
479 uint8_t type = sense[0] & 0x7f;
480 switch(type)
481 {
482 case 0x70: case 0x73: rb_printf("Current sense: "); break;
483 case 0x71: case 0x72: rb_printf("Previous sense: "); break;
484 default: rb_printf("Unknown sense\n"); return;
485 }
486 unsigned key = sense[2] & 0xf;
487 switch(key)
488 {
489 case 0: rb_printf("no sense"); break;
490 case 1: rb_printf("recovered error"); break;
491 case 2: rb_printf("not ready"); break;
492 case 3: rb_printf("medium error"); break;
493 case 4: rb_printf("hardware error"); break;
494 case 5: rb_printf("illegal request"); break;
495 case 6: rb_printf("unit attention"); break;
496 case 7: rb_printf("data protect"); break;
497 case 8: rb_printf("blank check"); break;
498 case 9: rb_printf("vendor specific"); break;
499 case 10: rb_printf("copy aborted"); break;
500 case 11: rb_printf("aborted command"); break;
501 default: rb_printf("unknown key"); break;
502 }
503 rb_printf("\n");
504
505#undef rb_printf
506}
507
508void rb_scsi_free_list(struct rb_scsi_devent_t *list)
509{
510 if(list == NULL)
511 return;
512 for(struct rb_scsi_devent_t *p = list; p->scsi_path; p++)
513 {
514 free(p->scsi_path);
515 if(p->block_path)
516 free(p->block_path);
517 if(p->vendor)
518 free(p->vendor);
519 if(p->model)
520 free(p->model);
521 if(p->rev)
522 free(p->rev);
523 }
524 free(list);
525}