/
usb_moded-modesetting.c
694 lines (597 loc) · 19.1 KB
1
2
3
4
/**
@file usb_moded-modesetting.c
Copyright (C) 2010 Nokia Corporation. All rights reserved.
5
Copyright (C) 2013-2016 Jolla Ltd.
6
7
@author: Philippe De Swert <philippe.de-swert@nokia.com>
8
9
10
11
@author: Philippe De Swert <philippe.deswert@jollamobile.com>
@author: Bernd Wachter <bernd.wachter@jollamobile.com>
@author: Slava Monich <slava.monich@jolla.com>
@author: Simo Piiroinen <simo.piiroinen@jollamobile.com>
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
This program is free software; you can redistribute it and/or
modify it under the terms of the Lesser GNU General Public License
version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the Lesser GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA
*/
28
#define _GNU_SOURCE
29
30
31
32
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
33
#include <stdbool.h>
34
#include <sys/stat.h>
35
#include <limits.h>
36
37
38
39
40
#include <glib.h>
#include "usb_moded.h"
#include "usb_moded-modules.h"
41
#include "usb_moded-modes.h"
42
43
44
45
46
47
#include "usb_moded-log.h"
#include "usb_moded-dbus.h"
#include "usb_moded-dbus-private.h"
#include "usb_moded-appsync.h"
#include "usb_moded-config.h"
#include "usb_moded-modesetting.h"
48
#include "usb_moded-network.h"
49
#include "usb_moded-android.h"
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
static char *read_from_file(const char *path, size_t maxsize);
static GHashTable *tracked_values = 0;
static void usb_moded_mode_track_value(const char *path, const char *text)
{
if( !tracked_values || !path )
goto EXIT;
if( text )
g_hash_table_replace(tracked_values, g_strdup(path), g_strdup(text));
else
g_hash_table_remove(tracked_values, path);
EXIT:
return;
}
void usb_moded_mode_verify_values(void)
{
GHashTableIter iter;
gpointer key, value;
if( !tracked_values )
goto EXIT;
g_hash_table_iter_init(&iter, tracked_values);
while( g_hash_table_iter_next(&iter, &key, &value) )
{
const char *path = key;
const char *text = value;
char *curr = read_from_file(path, 0x1000);
if( g_strcmp0(text, curr) ) {
/* There might be case mismatch between hexadecimal
* values used in configuration files vs what we get
* back when reading from kernel interfaces. */
if( text && curr && !g_ascii_strcasecmp(text, curr) ) {
91
log_debug("unexpected change '%s' : '%s' -> '%s' (case diff only)", path,
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
text ?: "???",
curr ?: "???");
}
else {
log_warning("unexpected change '%s' : '%s' -> '%s'", path,
text ?: "???",
curr ?: "???");
}
usb_moded_mode_track_value(path, curr);
}
free(curr);
}
EXIT:
return;
}
110
static void report_mass_storage_blocker(const char *mountpoint, int try);
111
static guint delayed_network = 0;
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
static char *strip(char *str)
{
unsigned char *src = (unsigned char *)str;
unsigned char *dst = (unsigned char *)str;
while( *src > 0 && *src <= 32 ) ++src;
for( ;; )
{
while( *src > 32 ) *dst++ = *src++;
while( *src > 0 && *src <= 32 ) ++src;
if( *src == 0 ) break;
*dst++ = ' ';
}
*dst = 0;
return str;
}
static char *read_from_file(const char *path, size_t maxsize)
{
int fd = -1;
ssize_t done = 0;
char *data = 0;
char *text = 0;
if((fd = open(path, O_RDONLY)) == -1)
{
/* Silently ignore things that could result
* from missing / read-only files */
if( errno != ENOENT && errno != EACCES )
log_warning("%s: open: %m", path);
goto cleanup;
}
if( !(data = malloc(maxsize + 1)) )
goto cleanup;
if((done = read(fd, data, maxsize)) == -1)
{
log_warning("%s: read: %m", path);
goto cleanup;
}
text = realloc(data, done + 1), data = 0;
text[done] = 0;
strip(text);
cleanup:
free(data);
if(fd != -1) close(fd);
return text;
}
166
167
int write_to_file_real(const char *file, int line, const char *func,
const char *path, const char *text)
168
169
170
{
int err = -1;
int fd = -1;
171
size_t todo = 0;
172
char *prev = 0;
173
bool clear = false;
175
176
177
178
179
/* if either path or the text to be written are not there
we return an error */
if(!text || !path)
return err;
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
/* When attempting to clear ffs function list, writing an
* empty string is ignored and accomplishes nothing - while
* writing non-existing function clears the list but returns
* write error.
*
* Treat "none" (which is used as place-holder value in both
* configuration files and usb-moded sources) and "" similarly:
* - Write invalid function name to sysfs
* - Ignore resulting write error under default logging level
* - Assume reading from sysfs will result in empty string
*/
if( !strcmp(path, "/sys/class/android_usb/android0/functions") ) {
if( !strcmp(text, "") || !strcmp(text, "none") ) {
text = "none";
clear = true;
}
198
199
200
/* If the file can be read, it also means we can later check that
* the file retains the value we are about to write here. */
if( (prev = read_from_file(path, 0x1000)) )
201
usb_moded_mode_track_value(path, clear ? "" : text);
202
203
204
205
log_debug("%s:%d: %s(): WRITE '%s' : '%s' --> '%s'",
file, line, func,
path, prev ?: "???", text);
207
todo = strlen(text);
208
209
210
211
/* no O_CREAT -> writes only to already existing files */
if( (fd = TEMP_FAILURE_RETRY(open(path, O_WRONLY))) == -1 )
{
212
log_warning("open(%s): %m", path);
213
214
215
216
217
218
219
220
goto cleanup;
}
while( todo > 0 )
{
ssize_t n = TEMP_FAILURE_RETRY(write(fd, text, todo));
if( n < 0 )
{
221
222
223
224
if( clear && errno == EINVAL )
log_debug("write(%s): %m (expected failure)", path);
else
log_warning("write(%s): %m", path);
225
226
227
228
229
230
231
232
233
234
235
236
goto cleanup;
}
todo -= n;
text += n;
}
err = 0;
cleanup:
if( fd != -1 ) TEMP_FAILURE_RETRY(close(fd));
237
238
free(prev);
239
240
241
return err;
}
242
243
static gboolean network_retry(gpointer data)
{
244
delayed_network = 0;
245
246
247
248
usb_network_up(data);
return(FALSE);
}
249
static int set_mass_storage_mode(struct mode_list_elem *data)
250
251
{
gchar *command;
252
char command2[256], *real_path = NULL, *mountpath;
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
gchar **mounts;
int ret = 0, i = 0, mountpoints = 0, fua = 0, try = 0;
/* send unmount signal so applications can release their grasp on the fs, do this here so they have time to act */
usb_moded_send_signal(USB_PRE_UNMOUNT);
fua = find_sync();
mount = find_mounts();
if(mount)
{
mounts = g_strsplit(mount, ",", 0);
/* check amount of mountpoints */
for(i=0 ; mounts[i] != NULL; i++)
{
mountpoints++;
}
270
271
272
273
274
275
276
277
278
279
280
if(strcmp(data->mode_module, MODULE_NONE))
{
/* check if the file storage module has been loaded with sufficient luns in the parameter,
if not, unload and reload or load it. Since mountpoints start at 0 the amount of them is one more than their id */
sprintf(command2, "/sys/devices/platform/musb_hdrc/gadget/gadget-lun%d/file", (mountpoints - 1) );
if(access(command2, R_OK) == -1)
{
log_debug("%s does not exist, unloading and reloading mass_storage\n", command2);
usb_moded_unload_module(MODULE_MASS_STORAGE);
sprintf(command2, "modprobe %s luns=%d \n", MODULE_MASS_STORAGE, mountpoints);
log_debug("usb-load command = %s \n", command2);
281
ret = usb_moded_system(command2);
282
283
284
285
if(ret)
return(ret);
}
}
286
287
288
289
/* umount filesystems */
for(i=0 ; mounts[i] != NULL; i++)
{
/* check if filesystem is mounted or not, if ret = 1 it is already unmounted */
290
291
292
293
294
295
real_path = realpath(mounts[i], NULL);
if(real_path)
mountpath = real_path;
else
mountpath = mounts[i];
umount: command = g_strconcat("mount | grep ", mountpath, NULL);
296
ret = usb_moded_system(command);
297
298
299
g_free(command);
if(!ret)
{
300
/* no check for / needed as that will fail to umount anyway */
301
command = g_strconcat("umount ", mountpath, NULL);
302
log_debug("unmount command = %s\n", command);
303
ret = usb_moded_system(command);
304
305
306
g_free(command);
if(ret != 0)
{
307
if(try != 3)
308
309
{
try++;
310
usb_moded_sleep(1);
311
log_err("Umount failed. Retrying\n");
312
report_mass_storage_blocker(mount, 1);
313
314
315
316
317
goto umount;
}
else
{
log_err("Unmounting %s failed\n", mount);
318
report_mass_storage_blocker(mount, 2);
319
320
321
322
323
324
325
326
327
328
usb_moded_send_error_signal(UMOUNT_ERROR);
return(ret);
}
}
}
else
/* already unmounted. Set return value to 0 since there is no error */
ret = 0;
}
329
/* activate mounts after sleeping 1s to be sure enumeration happened and autoplay will work in windows*/
330
usb_moded_sleep(1);
331
for(i=0 ; mounts[i] != NULL; i++)
333
334
335
336
337
if(strcmp(data->mode_module, MODULE_NONE))
{
sprintf(command2, "echo %i > /sys/devices/platform/musb_hdrc/gadget/gadget-lun%d/nofua", fua, i);
log_debug("usb lun = %s active\n", command2);
338
usb_moded_system(command2);
339
340
341
342
343
344
345
346
347
348
349
350
351
sprintf(command2, "/sys/devices/platform/musb_hdrc/gadget/gadget-lun%d/file", i);
log_debug("usb lun = %s active\n", command2);
write_to_file(command2, mounts[i]);
}
else
{
write_to_file("/sys/class/android_usb/android0/enable", "0");
write_to_file("/sys/class/android_usb/android0/functions", "mass_storage");
//write_to_file("/sys/class/android_usb/f_mass_storage/lun/nofua", fua);
write_to_file("/sys/class/android_usb/f_mass_storage/lun/file", mount);
write_to_file("/sys/class/android_usb/android0/enable", "1");
}
352
353
}
g_strfreev(mounts);
354
g_free(mount);
355
356
if(real_path)
free(real_path);
357
358
359
360
361
362
363
364
365
366
}
/* only send data in use signal in case we actually succeed */
if(!ret)
usb_moded_send_signal(DATA_IN_USE);
return(ret);
}
367
static int unset_mass_storage_mode(struct mode_list_elem *data)
368
369
{
gchar *command;
370
char command2[256], *real_path = NULL, *mountpath;
372
373
374
375
376
377
378
379
380
381
gchar **mounts;
int ret = 1, i = 0;
mount = find_mounts();
if(mount)
{
mounts = g_strsplit(mount, ",", 0);
for(i=0 ; mounts[i] != NULL; i++)
{
/* check if it is still or already mounted, if so (ret==0) skip mounting */
382
383
384
385
386
387
real_path = realpath(mounts[i], NULL);
if(real_path)
mountpath = real_path;
else
mountpath = mounts[i];
command = g_strconcat("mount | grep ", mountpath, NULL);
388
ret = usb_moded_system(command);
389
390
391
g_free(command);
if(ret)
{
392
command = g_strconcat("mount ", mountpath, NULL);
393
log_debug("mount command = %s\n",command);
394
ret = usb_moded_system(command);
395
g_free(command);
396
397
/* mount returns 0 if success */
if(ret != 0 )
398
399
{
log_err("Mounting %s failed\n", mount);
400
401
if(ret)
{
402
g_free(mount);
403
404
405
mount = find_alt_mount();
if(mount)
{
406
407
command = g_strconcat("mount -t tmpfs tmpfs -o ro --size=512K ", mount, NULL);
log_debug("Total failure, mount ro tmpfs as fallback\n");
408
ret = usb_moded_system(command);
409
410
g_free(command);
}
411
usb_moded_send_error_signal(RE_MOUNT_FAILED);
412
413
414
415
}
}
}
416
417
if(data != NULL)
{
418
if(!strcmp(data->mode_module, MODULE_NONE))
420
421
log_debug("Disable android mass storage\n");
write_to_file("/sys/class/android_usb/f_mass_storage/lun/file", "0");
422
423
424
write_to_file("/sys/class/android_usb/android0/enable", "0");
}
}
425
426
427
428
else
{
sprintf(command2, "echo \"\" > /sys/devices/platform/musb_hdrc/gadget/gadget-lun%d/file", i);
log_debug("usb lun = %s inactive\n", command2);
429
usb_moded_system(command2);
431
432
}
g_strfreev(mounts);
433
g_free(mount);
434
435
if(real_path)
free(real_path);
436
437
438
439
440
441
}
return(ret);
}
442
static void report_mass_storage_blocker(const char *mountpoint, int try)
443
444
445
446
447
448
449
{
FILE *stream = 0;
gchar *lsof_command = 0;
int count = 0;
lsof_command = g_strconcat("lsof ", mountpoint, NULL);
450
if( (stream = usb_moded_popen(lsof_command, "r")) )
451
452
453
454
455
456
457
458
459
460
461
{
char *text = 0;
size_t size = 0;
while( getline(&text, &size, stream) >= 0 )
{
/* skip the first line as it does not contain process info */
if(count != 0)
{
gchar **split = 0;
split = g_strsplit((const gchar*)text, " ", 2);
462
log_err("Mass storage blocked by process %s\n", split[0]);
463
464
465
466
467
468
469
470
usb_moded_send_error_signal(split[0]);
g_strfreev(split);
}
count++;
}
pclose(stream);
}
g_free(lsof_command);
471
472
if(try == 2)
log_err("Setting Mass storage blocked. Giving up.\n");
473
474
475
}
476
int set_dynamic_mode(void)
478
479
struct mode_list_elem *data;
480
int ret = 1;
481
int network = 1;
482
483
484
data = get_usb_mode_data();
486
return(ret);
488
if(data->mass_storage)
490
return set_mass_storage_mode(data);
493
#ifdef APP_SYNC
494
if(data->appsync)
495
if(activate_sync(data->mode_name)) /* returns 1 on error */
496
497
{
log_debug("Appsync failure");
498
return(ret);
501
/* make sure things are disabled before changing functionality */
502
if(data->softconnect_disconnect)
503
504
505
{
write_to_file(data->softconnect_path, data->softconnect_disconnect);
}
506
/* set functionality first, then enable */
507
508
if(data->android_extra_sysfs_value && data->android_extra_sysfs_path)
{
509
ret = write_to_file(data->android_extra_sysfs_path, data->android_extra_sysfs_value);
510
511
512
513
514
}
if(data->android_extra_sysfs_value2 && data->android_extra_sysfs_path2)
{
write_to_file(data->android_extra_sysfs_path2, data->android_extra_sysfs_value2);
}
515
516
517
518
if(data->sysfs_path)
{
write_to_file(data->sysfs_path, data->sysfs_value);
}
519
520
521
522
523
if(data->idProduct)
{
/* only works for android since the idProduct is a module parameter */
set_android_productid(data->idProduct);
}
524
525
526
527
528
529
if(data->idVendorOverride)
{
/* only works for android since the idProduct is a module parameter */
set_android_vendorid(data->idVendorOverride);
}
530
/* enable the device */
531
532
if(data->softconnect)
{
533
ret = write_to_file(data->softconnect_path, data->softconnect);
536
/* functionality should be enabled, so we can enable the network now */
537
if(data->network)
540
char command[256];
542
g_snprintf(command, 256, "ifdown %s ; ifup %s", data->network_interface, data->network_interface);
543
usb_moded_system(command);
545
usb_network_down(data);
546
network = usb_network_up(data);
547
#endif /* DEBIAN */
550
551
/* try a second time to bring up the network if it failed the first time,
this can happen with functionfs based gadgets (which is why we sleep for a bit */
552
if(network != 0 && data->network)
554
log_debug("Retry setting up the network later\n");
555
556
if(delayed_network)
g_source_remove(delayed_network);
557
delayed_network = g_timeout_add_seconds(3, network_retry, data);
560
561
562
/* Needs to be called before application post synching so
that the dhcp server has the right config */
if(data->nat || data->dhcp_server)
563
usb_network_set_up_dhcpd(data);
564
565
566
/* no need to execute the post sync if there was an error setting the mode */
if(data->appsync && !ret)
567
568
{
/* let's sleep for a bit (350ms) to allow interfaces to settle before running postsync */
569
usb_moded_msleep(350);
570
activate_sync_post(data->mode_name);
573
574
575
576
577
#ifdef CONNMAN
if(data->connman_tethering)
connman_set_tethering(data->connman_tethering, TRUE);
#endif
578
579
580
if(ret)
usb_moded_send_error_signal(MODE_SETTING_FAILED);
return(ret);
583
584
585
586
587
588
void unset_dynamic_mode(void)
{
struct mode_list_elem *data;
data = get_usb_mode_data();
590
591
592
593
594
595
if(delayed_network)
{
g_source_remove(delayed_network);
delayed_network = 0;
}
596
597
598
599
/* the modelist could be empty */
if(!data)
return;
600
601
if(!strcmp(data->mode_name, MODE_MASS_STORAGE))
{
602
unset_mass_storage_mode(data);
603
604
605
return;
}
606
607
608
609
610
#ifdef CONNMAN
if(data->connman_tethering)
connman_set_tethering(data->connman_tethering, FALSE);
#endif
611
612
613
614
615
if(data->network)
{
usb_network_down(data);
}
616
/* disconnect before changing functionality */
617
if(data->softconnect_disconnect)
618
619
620
{
write_to_file(data->softconnect_path, data->softconnect_disconnect);
}
621
622
623
624
if(data->sysfs_path)
{
write_to_file(data->sysfs_path, data->sysfs_reset_value);
}
625
626
627
628
629
630
631
632
/* restore vendorid if the mode had an override */
if(data->idVendorOverride)
{
char *id;
id = get_android_vendor_id();
set_android_vendorid(id);
g_free(id);
}
633
634
635
636
637
638
/* enable after the changes have been made */
if(data->softconnect)
{
write_to_file(data->softconnect_path, data->softconnect);
}
640
641
642
643
644
645
646
647
648
/** clean up mode changes or extra actions to perform after a mode change
* @param module Name of module currently in use
* @return 0 on success, non-zero on failure
*
*/
int usb_moded_mode_cleanup(const char *module)
{
649
650
log_debug("Cleaning up mode\n");
651
652
if(!module)
{
653
log_warning("No module found to unload. Skipping cleanup\n");
654
655
656
return 0;
}
657
#ifdef APP_SYNC
658
/* Stop applications started due to entering this mode */
659
appsync_stop(FALSE);
660
#endif /* APP_SYNC */
662
if(!strcmp(module, MODULE_MASS_STORAGE)|| !strcmp(module, MODULE_FILE_STORAGE))
664
665
/* no clean-up needs to be done when we come from charging mode. We need
to check since we use fake mass-storage for charging */
666
if(!strcmp(MODE_CHARGING, get_usb_mode()) || !strcmp(MODE_CHARGING_FALLBACK, get_usb_mode()))
668
unset_mass_storage_mode(NULL);
669
670
}
671
else if(get_usb_mode_data())
672
unset_dynamic_mode();
674
return(0);
675
676
}
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
/** Allocate modesetting related dynamic resouces
*/
void usb_moded_mode_init(void)
{
if( !tracked_values ) {
tracked_values = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, g_free);
}
}
/** Release modesetting related dynamic resouces
*/
void usb_moded_mode_quit(void)
{
if( tracked_values ) {
g_hash_table_unref(tracked_values), tracked_values = 0;
}
}