ApraLinuxUtils 1.0.0
C++ utility library for embedded Linux systems
 
Loading...
Searching...
No Matches
StorageUSB.cpp
Go to the documentation of this file.
1/*
2 * StorageUSB.cpp
3 *
4 * Copyright (c) 2024 Apra Labs
5 *
6 * This file is part of ApraUtils.
7 *
8 * Licensed under the MIT License.
9 * See LICENSE file in the project root for full license information.
10 */
11#include <fcntl.h>
12#include <inttypes.h>
13#include <libudev.h>
14#include <sys/mount.h>
15#include <sys/stat.h>
16#include <stdlib.h>
17#include <err.h>
18#include <stdexcept>
19#include <fstream>
20#include <sstream>
21#include <cstdlib>
22#include <cstring>
23#include <unistd.h>
24#include <sys/statvfs.h>
25#include "utils/Utils.h"
26#include <utils/StorageUSB.h>
27#include "utils/Macro.h"
28#include <algorithm>
29#include <iostream>
30
31#define MAX_BUF_LEN 1024
32
33using namespace std;
34namespace apra
35{
36
37StorageUSB::StorageUSB(string mountPath, vector<STORAGE_TYPE> supportedTypes,
38 bool shouldPrint, bool skipMount) :
39 m_shouldPrint(shouldPrint), m_supportedTypes(supportedTypes), m_mountPoint(
40 mountPath), m_deviceNode(), m_partitionNode(), m_skipMount(
41 skipMount), m_state(STORAGE_SAFE_EJECT), m_manualPath(mountPath), m_retryCount(3)
42{
43 string error;
44 if (m_supportedTypes.empty())
45 {
46 error = "Supported filesytem type(supportedTypes) parameter is empty!";
47 throw std::invalid_argument(error.c_str());
48 }
49 if (!skipMount)
50 {
51 if (m_mountPoint.empty())
52 {
53 error = "Path to mount(mountPath) parameter is empty!";
54 throw std::invalid_argument(error.c_str());
55 }
56 }
57}
58
62
67
69{
70 string mountPath;
72 {
73 mountPath = m_mountPoint;
74 }
75 return mountPath;
76}
77
78struct udev_device* StorageUSB::getChildDevice(struct udev *udev,
79 struct udev_device *parent, const char *subsystem)
80{
81 struct udev_device *child = NULL;
82 struct udev_enumerate *enumerate = udev_enumerate_new(udev);
83 udev_enumerate_add_match_parent(enumerate, parent);
84 udev_enumerate_add_match_subsystem(enumerate, subsystem);
85 udev_enumerate_scan_devices(enumerate);
86 struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate);
87 struct udev_list_entry *entry;
88 udev_list_entry_foreach(entry, devices)
89 {
90 const char *path = udev_list_entry_get_name(entry);
91 child = udev_device_new_from_syspath(udev, path);
92 break;
93 }
94 udev_enumerate_unref(enumerate);
95 return child;
96}
97
98string StorageUSB::enumerateDevices(struct udev *udev)
99{
100 string deviceID;
101 struct udev_enumerate *enumerate = udev_enumerate_new(udev);
102 udev_enumerate_add_match_subsystem(enumerate, "scsi");
103 udev_enumerate_add_match_property(enumerate, "DEVTYPE", "scsi_device");
104 udev_enumerate_scan_devices(enumerate);
105 struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate);
106 struct udev_list_entry *entry;
107
108 udev_list_entry_foreach(entry, devices)
109 {
110 if (deviceID.empty())
111 {
112 const char *path = udev_list_entry_get_name(entry);
113 struct udev_device *scsi = udev_device_new_from_syspath(udev, path);
114 struct udev_device *block = getChildDevice(udev, scsi, "block");
115 struct udev_device *scsiDisk = getChildDevice(udev, scsi,
116 "scsi_disk");
117
118 struct udev_device *usb =
119 udev_device_get_parent_with_subsystem_devtype(scsi, "usb",
120 "usb_device");
121 if (block && scsiDisk && usb)
122 {
123 deviceID.assign(udev_device_get_devnode(block));
124 }
125 if (block)
126 {
127 udev_device_unref(block);
128 }
129 if (scsiDisk)
130 {
131 udev_device_unref(scsiDisk);
132 }
133 udev_device_unref(scsi);
134 }
135 }
136 udev_enumerate_unref(enumerate);
137 return deviceID;
138}
139
141{
143 {
144 struct udev *udev = udev_new();
145 string devicePath = enumerateDevices(udev);
146 udev_unref(udev);
147 if (devicePath.length())
148 {
150 m_deviceNode = devicePath;
151 }
152 if (m_shouldPrint)
153 {
154 std::cout << "Device Node is ==========>>>" << m_deviceNode << std::endl;
155 }
156 return devicePath;
157 }
158 return m_deviceNode;
159}
160
162{
163 string usbMountPath;
164 string devNode = insertCheck();
165 bool isSupportedFs = false;
166 if (m_skipMount)
167 {
168 StorageMinimalInfo highPartition = getHighCapacityPartition(devNode);
169 if (highPartition.m_size > 0)
170 {
171 usbMountPath = findMountDeviceBylsblk(highPartition.m_partition);
172 m_mountPoint = usbMountPath;
173 m_partitionNode = highPartition.m_partition;
174 }
176 auto it = std::find(m_supportedTypes.begin(),
177 m_supportedTypes.end(),
178 type);
179 isSupportedFs = (it != m_supportedTypes.end());
180 m_retryCount--;
181 }
182 else
183 {
184 if (mountDeviceNode(devNode))
185 {
186 usbMountPath = m_mountPoint;
187 }
188 }
189 if (!isSupportedFs)
190 {
192 }
193 else if (!usbMountPath.empty())
194 {
196 }
197 else if (!isSupportedFs && !m_retryCount)
198 {
200 m_retryCount = 0;
201 }
202 else
203 {
204 if (m_shouldPrint)
205 {
206 printf("USB mount path is empty\n");
207 }
208 }
209 return usbMountPath;
210}
211
213{
215 {
216 return true;
217 }
219 {
220 return false;
221 }
222 /* struct statvfs stat;
223 int64_t status = statvfs(m_mountPoint.c_str(), &stat);
224 if (m_shouldPrint || true)
225 {
226 printf("statvfs response = %" PRId64 " id=%lu\n", status, stat.f_fsid);
227 }
228 if (status != 0)
229 {
230 m_state = UNSAFE_EJECT;
231 return true;
232 }*/
234 {
236 m_deviceNode.clear();
237 m_retryCount = 3;
238 return true;
239 }
240 return false;
241
242}
243
245{
246 StorageMinimalInfo highCapacityPartition;
247 if (!deviceNode.empty())
248 {
249 vector<StorageMinimalInfo> partitions = getPartitions(deviceNode);
250 for (uint32_t count = 0; count < partitions.size(); count++)
251 {
252 if (highCapacityPartition.m_size < partitions[count].m_size)
253 {
254 highCapacityPartition = partitions[count];
255 }
256 }
257 }
258 if (m_shouldPrint)
259 {
260 std::cout << "High Capacity Partition is " << highCapacityPartition.m_partition << " == " << highCapacityPartition.m_fsType << " === " << highCapacityPartition.m_size << std::endl;
261 }
262 return highCapacityPartition;
263}
264
266{
267 int fd = open(m_mountPoint.c_str(), O_RDONLY);
268
269 if (fd == -1)
270 {
271 // Error occurred, USB device is likely not connected
272 return false;
273 }
274
275 close(fd);
276 return true;
277}
278
279vector<StorageMinimalInfo> StorageUSB::getPartitions(string devpath)
280{
281 std::vector<StorageMinimalInfo> partitions;
282
283 string listPartitionCommand =
284 "lsblk " + devpath
285 + " -b --noheadings --raw -o NAME,SIZE,FSTYPE | awk '$1~/.*[0-9]+$/ && $7==\"\"'";
286 string cmdResponse;
287 try
288 {
289 cmdResponse = apra::Utils::exec(listPartitionCommand, m_shouldPrint);
290 } catch (std::runtime_error &e)
291 {
292 printf("list partition error: %s\n", e.what());
293 return partitions;
294 }
295 if (cmdResponse.empty())
296 {
297 return partitions;
298 }
299 if (Utils::caseInsensitiveSearch(cmdResponse, "not a block device"))
300 {
301 return partitions;
302 }
303
304 istringstream issResponse(cmdResponse);
305 string line;
306 while (getline(issResponse, line))
307 {
308 char name[MAX_BUF_LEN] = { 0 }, size[MAX_BUF_LEN] = { 0 },
309 fstype[MAX_BUF_LEN] = { 0 };
310 int ret = sscanf(line.c_str(), "%s %s %s", name, size, fstype);
311 if (ret != 3)
312 {
313 continue;
314 }
315 // Add partition to the vector
316 partitions.push_back(
317 StorageMinimalInfo("/dev/" + string(name), std::stoull(size),
318 fstype));
319 }
320
321 return partitions;
322}
323
325 uint8_t retryLimit)
326{
327 string storageMountCheck = findMountDeviceBylsblk(
328 storageDevice.m_partition);
329 if (storageMountCheck.length())
330 {
331 m_mountPoint = storageMountCheck;
332 m_partitionNode = storageDevice.m_partition;
334 }
335
336 bool mountStatus = mountWithPrivilege(storageDevice, retryLimit);
337 if (mountStatus)
338 {
340 }
341 else
342 {
343 if (m_shouldPrint)
344 {
345 printf("Privilege mount did not work\n");
346 }
347 mountStatus = mountWithoutPrivilege(storageDevice);
348 if (mountStatus)
349 {
351 }
352 else
353 {
354 if (m_shouldPrint)
355 {
356 printf("Non-Privilege mount did not work\n");
357 }
358 }
359 }
360 return mountStatus;
361}
362
363bool StorageUSB::mountDeviceNode(string deviceNode)
364{
365 bool ret = false;
366 StorageMinimalInfo highStorage;
367 if (deviceNode.length())
368 {
369 highStorage = getHighCapacityPartition(deviceNode);
370 }
371 if (highStorage.m_partition.length() > 0)
372 {
373 ret = mountUSBDevice(highStorage);
374 }
375 else
376 {
377 if (m_shouldPrint)
378 {
379 printf("No Partitions found to mount\n");
380 }
381 }
382 if (ret)
383 {
384 m_deviceNode = deviceNode;
385 m_partitionNode = highStorage.m_partition;
386 }
387 return ret;
388}
389
391 uint8_t retryLimit)
392{
393 struct stat st = { 0 };
394 if (stat(m_mountPoint.c_str(), &st) == -1)
395 {
396 int64_t retVal = mkdir(m_mountPoint.c_str(), 0777);
397 if (retVal == -1 && m_shouldPrint)
398 {
399 perror("directory cannot be created\n");
400 return false;
401 }
402 }
403 int64_t mountStatus = mount(storageDevice.m_partition.c_str(),
404 m_mountPoint.c_str(), storageDevice.m_fsType.c_str(), MS_NOATIME,
405 NULL);
406 if (mountStatus == 0)
407 {
408 if (m_shouldPrint)
409 {
410 printf("Mount successful\n");
411 }
412 return true;
413 }
414 else
415 {
416 if (m_shouldPrint)
417 {
418 printf("mount unsuccessful(%" PRId64 ") %s -> %s\n", mountStatus,
419 storageDevice.m_partition.c_str(), m_mountPoint.c_str());
420 }
421 }
422 string error = "";
423 if (mountStatus == EBUSY)
424 {
425 error = "Mountpoint busy. Let's retry in next " + to_string(retryLimit)
426 + " iteration";
428 }
429 else
430 {
431 error = "Mount error: " + to_string(errno);
432 }
433 if (m_shouldPrint)
434 {
435 printf("%s\n", error.c_str());
436 }
437 if (retryLimit-- == 0)
438 {
439 return false;
440 }
441 if (m_shouldPrint)
442 {
443 printf("mount retry left %" PRIu8 "\n", retryLimit);
444 }
445 usleep(1000);
446 return mountWithPrivilege(storageDevice, retryLimit);
447}
448
450{
451 try
452 {
453 string cmdResponse = Utils::exec(
454 "udisksctl mount --no-user-interaction -b "
455 + storageDevice.m_partition, m_shouldPrint);
456 if (m_shouldPrint)
457 {
458 printf("cmdResponse %s\n", cmdResponse.c_str());
459 }
460 if (apra::Utils::caseInsensitiveSearch(cmdResponse, "mounted"))
461 {
462 return true;
463 }
464 } catch (std::runtime_error &e)
465 {
466 printf("mount error: %s\n", e.what());
467 }
468 return false;
469}
470
472{
473 if (umount2(m_mountPoint.c_str(), MNT_FORCE))
474 {
475 string error;
476 if (errno == EBUSY)
477 {
478 error = "Unmount busy";
479 }
480 else
481 {
482 error = "Unmount error: " + to_string(errno);
483 }
484 if (m_shouldPrint)
485 {
486 printf("%s\n", error.c_str());
487 }
488 return false;
489 }
490 if (m_shouldPrint)
491 {
492 printf("Unmount successful\n");
493 }
494 return true;
495}
496
498{
499 try
500 {
501 string cmdResponse = Utils::exec(
502 "udisksctl unmount -f -b " + m_partitionNode, m_shouldPrint);
503 if (!apra::Utils::caseInsensitiveSearch(cmdResponse, "error"))
504 {
505 return true;
506 }
507 } catch (std::runtime_error &e)
508 {
509 printf("unmount error: %s\n", e.what());
510 }
511 return false;
512}
513
515{
517 {
519 }
520 return true;
521}
522
524{
525 if (unMountUSBDevice())
526 {
527 string ejectCommand = "";
529 m_retryCount = 3;
530 m_mountPoint.clear();
531 m_deviceNode.clear();
532 m_partitionNode.clear();
533 return true;
534 }
535 return false;
536}
537
538string StorageUSB::findMountDeviceBylsblk(string devicePartitionNode)
539{
540 try
541 {
542 string cmdResponse = Utils::exec(
543 "lsblk " + devicePartitionNode + " --noheadings -o MOUNTPOINT",
545 string mountPath = Utils::trim(cmdResponse);
546 if (m_shouldPrint)
547 {
548 std::cout<<"\t mountPath ->" <<mountPath.c_str()<<std::endl;
549 }
550 if (mountPath.length()
551 && Utils::caseInsensitiveSearch(mountPath,
552 "not a block device"))
553 {
554 mountPath.clear();
555 }
556 return mountPath;
557 } catch (std::runtime_error &e)
558 {
559 printf("lsblk error: %s\n", e.what());
560 }
561 return "";
562}
563
564string StorageUSB::findMountedDevice(string devicePartitionNode)
565{
566 std::string deviceName = devicePartitionNode.substr(
567 devicePartitionNode.rfind("/") + 1);
568 std::ifstream mountsFile("/proc/mounts");
569 if (!mountsFile)
570 {
571 perror("Failed to open mounts file");
572 return "";
573 }
574 std::string line;
575 while (std::getline(mountsFile, line))
576 {
577 std::istringstream iss(line);
578 std::string dev_name, dir_name, type;
579 if (iss >> dev_name >> dir_name >> type)
580 {
581 if (dev_name == devicePartitionNode)
582 {
583 return dir_name;
584 }
585 }
586 }
587 return "";
588}
589
590bool StorageUSB::getStorageInfo(uint64_t &freeSpaceInMB,
591 uint64_t &totalCapacityInMB)
592{
593 struct statvfs stat;
594 if (m_shouldPrint)
595 {
596 printf("path to stat %s\n", m_mountPoint.c_str());
597 }
598 if (statvfs(m_mountPoint.c_str(), &stat) != 0)
599 {
600 return false;
601 }
602 if (m_shouldPrint)
603 {
604 printf("stat.f_bsize=%" PRIu64 "\n stat.f_frsize=%" PRIu64 "\n stat.f_bfree=%" PRIu64 "\n "
605 "stat.f_blocks=%" PRIu64 "\n", stat.f_bsize, stat.f_frsize,
606 stat.f_bfree, stat.f_blocks);
607 }
608 uint64_t sz = stat.f_bsize;
609 if (m_shouldPrint)
610 {
611 printf("int sz = stat.f_bsize; === %" PRIu64 " = %" PRIu64 "\n", sz, stat.f_bsize);
612 printf("sz *= stat.f_bfree === %" PRIu64 " *= %" PRIu64 "\n", sz, stat.f_bfree);
613 }
614 sz *= stat.f_bfree;
615 freeSpaceInMB = sz >> 20;
616 if (m_shouldPrint)
617 {
618 printf("int freeSpaceInMB = sz >> 20 === int %" PRId64 " = %" PRId64 " >> 20\n",
619 freeSpaceInMB, sz);
620 }
621 sz = stat.f_frsize;
622
623 if (m_shouldPrint)
624 {
625 printf("sz = stat.f_bsize; === %" PRId64 " = %" PRId64 ";\n", sz, stat.f_bsize);
626 }
627
628 sz *= stat.f_blocks;
629
630 if (m_shouldPrint)
631 {
632 printf("sz *= stat.f_blocks; === %" PRIu64 " *= %" PRIu64 ";\n", sz,
633 stat.f_blocks);
634 }
635
636 totalCapacityInMB = sz >> 20;
637
638 if (m_shouldPrint)
639 {
640 printf("int totalSpaceInMB = sz >> 20; === int %" PRIu64 " = "
641 "%" PRIu64 " >> 20;\n", totalCapacityInMB, sz);
642 }
643 return true;
644}
645
647{
648 struct stat buffer;
649 string path = m_deviceNode;
650 if (path.length() && stat(path.c_str(), &buffer) == 0)
651 {
652 if (S_ISBLK(buffer.st_mode))
653 {
654 return;
655 }
656 }
658 m_deviceNode.clear();
659 m_retryCount = 3;
660}
661
662}
663/* namespace apra */
664
#define MAX_BUF_LEN
static STORAGE_TYPE getEnum(std::string typeStr)
bool isDeviceNodeConnected()
std::vector< StorageMinimalInfo > getPartitions(std::string devpath)
string findMountDeviceBylsblk(string devicePartitionNode)
bool unMountWithoutPrivilege()
STORAGE_STATE m_state
Definition StorageUSB.h:62
std::vector< STORAGE_TYPE > m_supportedTypes
Definition StorageUSB.h:57
virtual ~StorageUSB()
bool unMountWithPrivilege()
std::string m_mountPoint
Definition StorageUSB.h:58
bool getStorageInfo(uint64_t &freeSpaceInMB, uint64_t &totalCapacityInMB)
virtual string insertCheck()
virtual bool ejectDevice()
StorageUSB(std::string mountPath, std::vector< STORAGE_TYPE > supportedTypes, bool shouldPrint, bool skipMount=false)
virtual string mountDevice()
std::string getMountPath()
string findMountedDevice(string devicePartitionNode)
std::string m_manualPath
Definition StorageUSB.h:63
int8_t m_retryCount
Definition StorageUSB.h:64
std::string m_partitionNode
Definition StorageUSB.h:60
struct udev_device * getChildDevice(struct udev *udev, struct udev_device *parent, const char *subsystem)
StorageMinimalInfo getHighCapacityPartition(std::string deviceNode)
bool mountWithoutPrivilege(StorageMinimalInfo storageDevice)
STORAGE_STATE getStatus()
std::string enumerateDevices(struct udev *udev)
bool mountUSBDevice(StorageMinimalInfo storageDevice, uint8_t retryLimit=3)
std::string m_deviceNode
Definition StorageUSB.h:59
bool mountDeviceNode(string deviceNode)
bool mountWithPrivilege(StorageMinimalInfo storageDevice, uint8_t retryLimit=3)
virtual bool isUnsafeEject()
static string trim(string str)
Definition Utils.cpp:157
static string exec(const string &cmd, bool debug=false)
Definition Utils.cpp:64
static bool caseInsensitiveSearch(std::string const str, std::string const pattern)
Definition Utils.cpp:143
STORAGE_TYPE
Definition StorageType.h:19
STORAGE_STATE
@ STORAGE_MOUNTED
@ STORAGE_UNSAFE_EJECT
@ STORAGE_INSERTED_UNMOUNTED
@ STORAGE_INSERTED
@ STORAGE_SAFE_EJECT