1 /* ***** BEGIN LICENSE BLOCK *****
2 *   Copyright (C) 2012-2016, Peter Hatina <phatina@gmail.com>
3 *
4 *   This program is free software; you can redistribute it and/or
5 *   modify it under the terms of the GNU General Public License as
6 *   published by the Free Software Foundation; either version 2 of
7 *   the License, or (at your option) any later version.
8 *
9 *   This program is distributed in the hope that it will be useful,
10 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 *   GNU General Public License for more details.
13 *
14 *   You should have received a copy of the GNU General Public License
15 *   along with this program. If not, see <http://www.gnu.org/licenses/>.
16 * ***** END LICENSE BLOCK ***** */
17 
18 #include <config.h>
19 #include <algorithm>
20 #include <sstream>
21 #include <vector>
22 #include <cstring>
23 #include <cstdint>
24 #include <cstdlib>
25 extern "C" {
26 #  include <unistd.h>
27 #  include <sys/types.h>
28 #  define _DARWIN_USE_64_BIT_INODE
29 #  include <sys/stat.h>
30 }
31 #include "simple-mtpfs-fuse.h"
32 #include "simple-mtpfs-libmtp.h"
33 #include "simple-mtpfs-log.h"
34 #include "simple-mtpfs-mtp-device.h"
35 #include "simple-mtpfs-util.h"
36 
37 uint32_t MTPDevice::s_root_node = ~0;
38 
39 MTPDevice::MTPDevice():
40     m_device(nullptr),
41     m_capabilities(),
42     m_device_mutex(),
43     m_root_dir(),
44     m_move_enabled(false)
45 {
46     StreamHelper::off();
47     LIBMTP_Init();
48     StreamHelper::on();
49 }
50 
51 MTPDevice::~MTPDevice()
52 {
53     disconnect();
54 }
55 
56 bool MTPDevice::connect(LIBMTP_raw_device_t *dev)
57 {
58     if (m_device) {
59         logerr("Already connected.\n");
60         return true;
61     }
62 
63     // Do not output LIBMTP debug stuff
64     StreamHelper::off();
65     m_device = LIBMTP_Open_Raw_Device_Uncached(dev);
66     StreamHelper::on();
67 
68     if (!m_device) {
69         LIBMTP_Dump_Errorstack(m_device);
70         return false;
71     }
72 
73     if (!enumStorages())
74         return false;
75 
76     // Retrieve capabilities.
77     m_capabilities = MTPDevice::getCapabilities(*this);
78 
79     logmsg("Connected.\n");
80     return true;
81 }
82 
83 bool MTPDevice::connect_priv(int dev_no, const std::string &dev_file)
84 {
85     if (m_device) {
86         logerr("Already connected.\n");
87         return true;
88     }
89 
90     int raw_devices_cnt;
91     LIBMTP_raw_device_t *raw_devices;
92 
93     // Do not output LIBMTP debug stuff
94     StreamHelper::off();
95     LIBMTP_error_number_t err = LIBMTP_Detect_Raw_Devices(
96         &raw_devices, &raw_devices_cnt);
97     StreamHelper::on();
98 
99     if (err != LIBMTP_ERROR_NONE) {
100         switch(err) {
101         case LIBMTP_ERROR_NO_DEVICE_ATTACHED:
102             logerr("No raw devices found.\n");
103             break;
104         case LIBMTP_ERROR_CONNECTING:
105             logerr("There has been an error connecting. Exiting.\n");
106             break;
107         case LIBMTP_ERROR_MEMORY_ALLOCATION:
108             logerr("Encountered a Memory Allocation Error. Exiting.\n");
109             break;
110         case LIBMTP_ERROR_GENERAL:
111             logerr("General error occurred. Exiting.\n");
112             break;
113         case LIBMTP_ERROR_USB_LAYER:
114             logerr("USB Layer error occurred. Exiting.\n");
115             break;
116         default:
117             break;
118         }
119         return false;
120     }
121 
122 #ifndef HAVE_LIBUSB1
123     if (!dev_file.empty()) {
124         uint8_t bnum, dnum;
125         dev_no = raw_devices_cnt;
126 
127         if (smtpfs_usb_devpath(dev_file, &bnum, &dnum))
128             for (dev_no = 0; dev_no < raw_devices_cnt; ++dev_no)
129                 if (bnum == raw_devices[dev_no].bus_location &&
130                     dnum == raw_devices[dev_no].devnum)
131                     break;
132 
133         if (dev_no == raw_devices_cnt) {
134             logerr("Can not open such device '", dev_file, "'.\n");
135             free(static_cast<void*>(raw_devices));
136             return false;
137         }
138     }
139 #endif // !HAVE_LIBUSB1
140 
141     if (dev_no < 0 || dev_no >= raw_devices_cnt) {
142         logerr("Can not connect to device no. ", dev_no + 1, ".\n");
143         free(static_cast<void*>(raw_devices));
144         return false;
145     }
146 
147     LIBMTP_raw_device_t *raw_device = &raw_devices[dev_no];
148 
149     // Do not output LIBMTP debug stuff
150     StreamHelper::off();
151     m_device = LIBMTP_Open_Raw_Device_Uncached(raw_device);
152     StreamHelper::on();
153     free(static_cast<void*>(raw_devices));
154 
155     if (!m_device) {
156         LIBMTP_Dump_Errorstack(m_device);
157         return false;
158     }
159 
160     if (!enumStorages())
161         return false;
162 
163     // Retrieve capabilities.
164     m_capabilities = MTPDevice::getCapabilities(*this);
165 
166     logmsg("Connected.\n");
167     return true;
168 }
169 
170 bool MTPDevice::connect(int dev_no)
171 {
172     return connect_priv(dev_no, std::string());
173 }
174 
175 #ifdef HAVE_LIBUSB1
176 bool MTPDevice::connect(const std::string &dev_file)
177 {
178     if (m_device) {
179         logerr("Already connected.\n");
180         return true;
181     }
182 
183     LIBMTP_raw_device_t *raw_device = smtpfs_raw_device_new(dev_file);
184     if (!raw_device) {
185         logerr("Can not open such device '", dev_file, "'.\n");
186         return false;
187     }
188 
189     bool rval = connect(raw_device);
190 
191     // TODO:  Smart pointer with alloc, free hooks.
192     smtpfs_raw_device_free(raw_device);
193 
194     return rval;
195 }
196 #else
197 bool MTPDevice::connect(const std::string &dev_file)
198 {
199     return connect_priv(-1, dev_file);
200 }
201 #endif
202 
203 void MTPDevice::disconnect()
204 {
205     if (!m_device)
206         return;
207 
208     LIBMTP_Release_Device(m_device);
209     m_device = nullptr;
210     logmsg("Disconnected.\n");
211 }
212 
213 uint64_t MTPDevice::storageTotalSize() const
214 {
215     uint64_t total = 0;
216     for (LIBMTP_devicestorage_t *s = m_device->storage; s; s = s->next)
217         total += s->MaxCapacity;
218     return total;
219 }
220 
221 uint64_t MTPDevice::storageFreeSize() const
222 {
223     uint64_t free = 0;
224     for (LIBMTP_devicestorage_t *s = m_device->storage; s; s = s->next)
225         free += s->FreeSpaceInBytes;
226     return free;
227 }
228 
229 bool MTPDevice::enumStorages()
230 {
231     criticalEnter();
232     LIBMTP_Clear_Errorstack(m_device);
233     if (LIBMTP_Get_Storage(m_device, LIBMTP_STORAGE_SORTBY_NOTSORTED) < 0) {
234         std::cerr << "Could not retrieve device storage.\n";
235         std::cerr << "For android phones make sure the screen is unlocked.\n";
236         logerr("Could not retrieve device storage. Exiting.\n");
237         LIBMTP_Dump_Errorstack(m_device);
238         LIBMTP_Clear_Errorstack(m_device);
239         return false;
240     }
241     criticalLeave();
242     return true;
243 }
244 
245 const TypeDir *MTPDevice::dirFetchContent(std::string path)
246 {
247     if (!m_root_dir.isFetched()) {
248         for (LIBMTP_devicestorage_t *s = m_device->storage; s; s = s->next) {
249             m_root_dir.addDir(TypeDir(s_root_node, 0, s->id,
250                 std::string(s->StorageDescription)));
251             m_root_dir.setFetched();
252         }
253     }
254 
255     if (m_root_dir.dirCount() == 1)
256         path = '/' + m_root_dir.dirs().begin()->name() + path;
257 
258     if (path == "/")
259         return &m_root_dir;
260 
261     std::string member;
262     std::istringstream ss(path);
263     TypeDir *dir = &m_root_dir;
264     while (std::getline(ss, member, '/')) {
265         if (member.empty())
266             continue;
267 
268         const TypeDir *tmp = dir->dir(member);
269         if (!tmp && !dir->isFetched()) {
270             criticalEnter();
271             LIBMTP_file_t *content = LIBMTP_Get_Files_And_Folders(
272                 m_device, dir->storageid(), dir->id());
273             criticalLeave();
274             for (LIBMTP_file_t *f = content; f; f = f->next) {
275                 if (f->filetype == LIBMTP_FILETYPE_FOLDER)
276                     dir->addDir(TypeDir(f));
277                 else
278                     dir->addFile(TypeFile(f));
279             }
280             LIBMTP_Free_Files_And_Folders(&content);
281             dir->setFetched();
282             tmp = dir->dir(member);
283         }
284 
285         if (!tmp)
286             return nullptr;
287         dir = const_cast<TypeDir*>(tmp);
288     }
289 
290     if (dir->isFetched())
291         return dir;
292 
293     criticalEnter();
294     dir->setFetched();
295     LIBMTP_file_t *content = LIBMTP_Get_Files_And_Folders(
296         m_device, dir->storageid(), dir->id());
297     criticalLeave();
298     for (LIBMTP_file_t *f = content; f; f = f->next) {
299         if (f->filetype == LIBMTP_FILETYPE_FOLDER)
300             dir->addDir(TypeDir(f));
301         else
302             dir->addFile(TypeFile(f));
303     }
304     LIBMTP_Free_Files_And_Folders(&content);
305     return dir;
306 }
307 
308 int MTPDevice::dirCreateNew(const std::string &path)
309 {
310     const std::string tmp_basename(smtpfs_basename(path));
311     const std::string tmp_dirname(smtpfs_dirname(path));
312     const TypeDir *dir_parent = dirFetchContent(tmp_dirname);
313     if (!dir_parent || dir_parent->id() == 0) {
314         logerr("Can not remove directory '", path, "'.\n");
315         return -EINVAL;
316     }
317     char *c_name = strdup(tmp_basename.c_str());
318     criticalEnter();
319     uint32_t new_id = LIBMTP_Create_Folder(m_device, c_name,
320         dir_parent->id(), dir_parent->storageid());
321     criticalLeave();
322     if (new_id == 0) {
323         logerr("Could not create directory '", path, "'.\n");
324         LIBMTP_Dump_Errorstack(m_device);
325         LIBMTP_Clear_Errorstack(m_device);
326     } else {
327         const_cast<TypeDir*>(dir_parent)->addDir(TypeDir(new_id, dir_parent->id(),
328             dir_parent->storageid(), tmp_basename));
329         logmsg("Directory '", path, "' created.\n");
330     }
331     free(static_cast<void*>(c_name));
332     return new_id != 0 ? 0 : -EINVAL;
333 }
334 
335 int MTPDevice::dirRemove(const std::string &path)
336 {
337     const std::string tmp_basename(smtpfs_basename(path));
338     const std::string tmp_dirname(smtpfs_dirname(path));
339     const TypeDir *dir_parent = dirFetchContent(tmp_dirname);
340     const TypeDir *dir_to_remove = dir_parent ? dir_parent->dir(tmp_basename) : nullptr;
341     if (!dir_parent || !dir_to_remove || dir_parent->id() == 0) {
342         logerr("No such directory '", path, "' to remove.\n");
343         return -ENOENT;
344     }
345     if (!dir_to_remove->isEmpty())
346         return -ENOTEMPTY;
347     criticalEnter();
348     int rval = LIBMTP_Delete_Object(m_device, dir_to_remove->id());
349     criticalLeave();
350     if (rval != 0){
351         logerr("Could not remove the directory '", path, "'.\n");
352         LIBMTP_Dump_Errorstack(m_device);
353         LIBMTP_Clear_Errorstack(m_device);
354         return -EINVAL;
355     }
356     const_cast<TypeDir*>(dir_parent)->removeDir(*dir_to_remove);
357     logmsg("Folder '", path, "' removed.\n");
358     return 0;
359 }
360 
361 int MTPDevice::dirRename(const std::string &oldpath, const std::string &newpath)
362 {
363     const std::string tmp_old_basename(smtpfs_basename(oldpath));
364     const std::string tmp_old_dirname(smtpfs_dirname(oldpath));
365     const std::string tmp_new_basename(smtpfs_basename(newpath));
366     const std::string tmp_new_dirname(smtpfs_dirname(newpath));
367     const TypeDir *dir_parent = dirFetchContent(tmp_old_dirname);
368     const TypeDir *dir_to_rename = dir_parent ? dir_parent->dir(tmp_old_basename) : nullptr;
369     if (!dir_parent || !dir_to_rename || dir_parent->id() == 0) {
370         logerr("Can not rename '", tmp_old_basename, "' to '",
371             tmp_new_basename, "'.\n");
372         return -EINVAL;
373     }
374     if (tmp_old_dirname != tmp_new_dirname) {
375         logerr("Can not move '", oldpath, "' to '", newpath, "'.\n");
376         return -EINVAL;
377     }
378 
379     LIBMTP_folder_t *folder = dir_to_rename->toLIBMTPFolder();
380     criticalEnter();
381     int ret = LIBMTP_Set_Folder_Name(m_device, folder, tmp_new_basename.c_str());
382     criticalLeave();
383     free(static_cast<void*>(folder->name));
384     free(static_cast<void*>(folder));
385     if (ret != 0) {
386         logerr("Could not rename '", oldpath, "' to '",  tmp_new_basename, "'.\n");
387         LIBMTP_Dump_Errorstack(m_device);
388         LIBMTP_Clear_Errorstack(m_device);
389         return -EINVAL;
390     }
391     const_cast<TypeDir*>(dir_to_rename)->setName(tmp_new_basename);
392     logmsg("Directory '", oldpath, "' renamed to '", tmp_new_basename, "'.\n");
393     return 0;
394 }
395 
396 int MTPDevice::rename(const std::string &oldpath, const std::string &newpath)
397 {
398 #ifndef SMTPFS_MOVE_BY_SET_OBJECT_PROPERTY
399     const std::string tmp_old_basename(smtpfs_basename(oldpath));
400     const std::string tmp_old_dirname(smtpfs_dirname(oldpath));
401     const std::string tmp_new_dirname(smtpfs_dirname(newpath));
402     if (tmp_old_dirname != tmp_new_dirname)
403         return -EINVAL;
404 
405     const TypeDir *dir_parent = dirFetchContent(tmp_old_dirname);
406     if (!dir_parent || dir_parent->id() == 0)
407         return -EINVAL;
408     const TypeDir *dir_to_rename = dir_parent->dir(tmp_old_basename);
409     if (dir_to_rename)
410         return dirRename(oldpath, newpath);
411     else
412         return fileRename(oldpath, newpath);
413 #else
414     const std::string tmp_old_basename(smtpfs_basename(oldpath));
415     const std::string tmp_old_dirname(smtpfs_dirname(oldpath));
416     const std::string tmp_new_basename(smtpfs_basename(newpath));
417     const std::string tmp_new_dirname(smtpfs_dirname(newpath));
418     const TypeDir *dir_old_parent = dirFetchContent(tmp_old_dirname);
419     const TypeDir *dir_new_parent = dirFetchContent(tmp_new_dirname);
420     const TypeDir *dir_to_rename = dir_old_parent ? dir_old_parent->dir(tmp_old_basename) : nullptr;
421     const TypeFile *file_to_rename = dir_old_parent ? dir_old_parent->file(tmp_old_basename) : nullptr;
422 
423     logdebug("dir_to_rename:    ", dir_to_rename, "\n");
424     logdebug("file_to_rename:   ", file_to_rename, "\n");
425 
426     if (!dir_old_parent || !dir_new_parent || dir_old_parent->id() == 0)
427         return -EINVAL;
428 
429     const TypeBasic *object_to_rename =  dir_to_rename ?
430         static_cast<const TypeBasic*>(dir_to_rename) :
431         static_cast<const TypeBasic*>(file_to_rename);
432 
433     logdebug("object_to_rename: ", object_to_rename, "\n");
434     logdebug("object_to_rename->id(): ", object_to_rename->id(), "\n");
435 
436     if (!object_to_rename) {
437         logerr("No such file or directory to rename/move!\n");
438         return -ENOENT;
439     }
440 
441     if (tmp_old_dirname != tmp_new_dirname) {
442         criticalEnter();
443         int rval = LIBMTP_Set_Object_u32(m_device, object_to_rename->id(),
444             LIBMTP_PROPERTY_ParentObject, dir_new_parent->id());
445         criticalLeave();
446         if (rval != 0) {
447             logerr("Could not move '", oldpath, "' to '", newpath, "'.\n");
448             LIBMTP_Dump_Errorstack(m_device);
449             LIBMTP_Clear_Errorstack(m_device);
450             return -EINVAL;
451         }
452         const_cast<TypeBasic*>(object_to_rename)->setParent(dir_new_parent->id());
453     }
454     if (tmp_old_basename != tmp_new_basename) {
455         criticalEnter();
456         int rval = LIBMTP_Set_Object_String(m_device, object_to_rename->id(),
457             LIBMTP_PROPERTY_Name, tmp_new_basename.c_str());
458         criticalLeave();
459         if (rval != 0) {
460             logerr("Could not rename '", oldpath, "' to '", newpath, "'.\n");
461             LIBMTP_Dump_Errorstack(m_device);
462             LIBMTP_Clear_Errorstack(m_device);
463             return -EINVAL;
464         }
465     }
466     return 0;
467 #endif
468 }
469 
470 int MTPDevice::fileRead(const std::string &path, char *buf, size_t size,
471     off_t offset)
472 {
473     const std::string path_basename(smtpfs_basename(path));
474     const std::string path_dirname(smtpfs_dirname(path));
475     const TypeDir *dir_parent = dirFetchContent(path_dirname);
476     const TypeFile *file_to_fetch = dir_parent ?
477         dir_parent->file(path_basename) : nullptr;
478     if (!dir_parent) {
479         logerr("Can not fetch '", path, "'.\n");
480         return -EINVAL;
481     }
482     if (!file_to_fetch) {
483         logerr("No such file '", path, "'.\n");
484         return -ENOENT;
485     }
486 
487     // all systems clear
488     unsigned char *tmp_buf;
489     unsigned int tmp_size;
490     int rval = LIBMTP_GetPartialObject(m_device, file_to_fetch->id(),
491         offset, size, &tmp_buf, &tmp_size);
492     if (tmp_size > 0) {
493         memcpy(buf, tmp_buf, tmp_size);
494         free(tmp_buf);
495     }
496 
497     if (rval != 0)
498         return -EIO;
499     return tmp_size;
500 }
501 
502 int MTPDevice::fileWrite(const std::string &path, const char *buf, size_t size,
503     off_t offset)
504 {
505     const std::string path_basename(smtpfs_basename(path));
506     const std::string path_dirname(smtpfs_dirname(path));
507     const TypeDir *dir_parent = dirFetchContent(path_dirname);
508     const TypeFile *file_to_fetch = dir_parent ?
509         dir_parent->file(path_basename) : nullptr;
510     if (!dir_parent) {
511         logerr("Can not fetch '", path, "'.\n");
512         return -EINVAL;
513     }
514     if (!file_to_fetch) {
515         logerr("No such file '", path, "'.\n");
516         return -ENOENT;
517     }
518 
519     // all systems clear
520     int rval = LIBMTP_SendPartialObject(m_device, file_to_fetch->id(),
521         offset, (unsigned char *) buf, size);
522 
523     if (rval < 0)
524         return -EIO;
525     return size;
526 }
527 
528 int MTPDevice::filePull(const std::string &src, const std::string &dst)
529 {
530     const std::string src_basename(smtpfs_basename(src));
531     const std::string src_dirname(smtpfs_dirname(src));
532     const TypeDir *dir_parent = dirFetchContent(src_dirname);
533     const TypeFile *file_to_fetch = dir_parent ? dir_parent->file(src_basename) : nullptr;
534     if (!dir_parent) {
535         logerr("Can not fetch '", src, "'.\n");
536         return -EINVAL;
537     }
538     if (!file_to_fetch) {
539         logerr("No such file '", src, "'.\n");
540         return -ENOENT;
541     }
542     if (file_to_fetch->size() == 0) {
543         int fd = ::creat(dst.c_str(), S_IRUSR | S_IWUSR);
544         ::close(fd);
545     } else {
546         logmsg("Started fetching '", src, "'.\n");
547         criticalEnter();
548         int rval = LIBMTP_Get_File_To_File(m_device, file_to_fetch->id(),
549             dst.c_str(), nullptr, nullptr);
550         criticalLeave();
551         if (rval != 0) {
552             logerr("Could not fetch file '", src, "'.\n");
553             LIBMTP_Dump_Errorstack(m_device);
554             LIBMTP_Clear_Errorstack(m_device);
555             return -ENOENT;
556         }
557     }
558     logmsg("File fetched '", src, "'.\n");
559     return 0;
560 }
561 
562 int MTPDevice::filePush(const std::string &src, const std::string &dst)
563 {
564     const std::string dst_basename(smtpfs_basename(dst));
565     const std::string dst_dirname(smtpfs_dirname(dst));
566     const TypeDir *dir_parent = dirFetchContent(dst_dirname);
567     const TypeFile *file_to_remove = dir_parent ? dir_parent->file(dst_basename) : nullptr;
568     if (dir_parent && file_to_remove) {
569         criticalEnter();
570         int rval = LIBMTP_Delete_Object(m_device, file_to_remove->id());
571         criticalLeave();
572         if (rval != 0) {
573             logerr("Can not upload '", src, "' to '", dst, "'.\n");
574             return -EINVAL;
575         }
576     }
577 
578     struct stat file_stat;
579     stat(src.c_str(), &file_stat);
580     TypeFile file_to_upload(0, dir_parent->id(), dir_parent->storageid(),
581         dst_basename, static_cast<uint64_t>(file_stat.st_size), 0);
582     LIBMTP_file_t *f = file_to_upload.toLIBMTPFile();
583     if (file_stat.st_size)
584         logmsg("Started uploading '", dst, "'.\n");
585     criticalEnter();
586     int rval = LIBMTP_Send_File_From_File(m_device, src.c_str(), f, nullptr, nullptr);
587     criticalLeave();
588     if (rval != 0) {
589         logerr("Could not upload file '", src, "'.\n");
590         LIBMTP_Dump_Errorstack(m_device);
591         LIBMTP_Clear_Errorstack(m_device);
592         rval = -EINVAL;
593     } else {
594         file_to_upload.setId(f->item_id);
595         file_to_upload.setParent(f->parent_id);
596         file_to_upload.setStorage(f->storage_id);
597         file_to_upload.setName(std::string(f->filename));
598         file_to_upload.setModificationDate(file_stat.st_mtime);
599         if (file_to_remove)
600             const_cast<TypeDir*>(dir_parent)->replaceFile(*file_to_remove, file_to_upload);
601         else
602             const_cast<TypeDir*>(dir_parent)->addFile(file_to_upload);
603     }
604     free(static_cast<void*>(f->filename));
605     free(static_cast<void*>(f));
606     logmsg("File '", dst, (file_stat.st_size ? " uploaded" : " created"), ".\n");
607     return rval;
608 }
609 
610 int MTPDevice::fileRemove(const std::string &path)
611 {
612     const std::string tmp_basename(smtpfs_basename(path));
613     const std::string tmp_dirname(smtpfs_dirname(path));
614     const TypeDir *dir_parent = dirFetchContent(tmp_dirname);
615     const TypeFile *file_to_remove = dir_parent ? dir_parent->file(tmp_basename) : nullptr;
616     if (!dir_parent || !file_to_remove) {
617         logerr("No such file '", path, "' to remove.\n");
618         return -ENOENT;
619     }
620     criticalEnter();
621     int rval = LIBMTP_Delete_Object(m_device, file_to_remove->id());
622     criticalLeave();
623     if (rval != 0) {
624         logerr("Could not remove the directory '", path, "'.\n");
625         return -EINVAL;
626     }
627     const_cast<TypeDir*>(dir_parent)->removeFile(*file_to_remove);
628     logmsg("File '", path, "' removed.\n");
629     return 0;
630 }
631 
632 int MTPDevice::fileRename(const std::string &oldpath, const std::string &newpath)
633 {
634     const std::string tmp_old_basename(smtpfs_basename(oldpath));
635     const std::string tmp_old_dirname(smtpfs_dirname(oldpath));
636     const std::string tmp_new_basename(smtpfs_basename(newpath));
637     const std::string tmp_new_dirname(smtpfs_dirname(newpath));
638     const TypeDir *dir_parent = dirFetchContent(tmp_old_dirname);
639     const TypeFile *file_to_rename = dir_parent ? dir_parent->file(tmp_old_basename) : nullptr;
640     if (!dir_parent || !file_to_rename || tmp_old_dirname != tmp_new_dirname) {
641         logerr("Can not rename '", oldpath, "' to '", tmp_new_basename, "'.\n");
642         return -EINVAL;
643     }
644 
645     LIBMTP_file_t *file = file_to_rename->toLIBMTPFile();
646     criticalEnter();
647     int rval = LIBMTP_Set_File_Name(m_device, file, tmp_new_basename.c_str());
648     criticalLeave();
649     free(static_cast<void*>(file->filename));
650     free(static_cast<void*>(file));
651     if (rval > 0) {
652         logerr("Could not rename '", oldpath, "' to '", newpath, "'.\n");
653         LIBMTP_Dump_Errorstack(m_device);
654         LIBMTP_Clear_Errorstack(m_device);
655         return -EINVAL;
656     }
657     const_cast<TypeFile*>(file_to_rename)->setName(tmp_new_basename);
658     logmsg("File '", oldpath, "' renamed to '", tmp_new_basename, "'.\n");
659     return 0;
660 }
661 
662 MTPDevice::Capabilities MTPDevice::getCapabilities() const
663 {
664     return m_capabilities;
665 }
666 
667 MTPDevice::Capabilities MTPDevice::getCapabilities(const MTPDevice &device)
668 {
669     MTPDevice::Capabilities capabilities;
670 
671 #ifdef HAVE_LIBMTP_CHECK_CAPABILITY
672     if (device.m_device) {
673         capabilities.setCanGetPartialObject(
674             static_cast<bool>(
675                 LIBMTP_Check_Capability(
676                     device.m_device,
677                     LIBMTP_DEVICECAP_GetPartialObject)));
678         capabilities.setCanSendPartialobject(
679             static_cast<bool>(
680                 LIBMTP_Check_Capability(
681                     device.m_device,
682                     LIBMTP_DEVICECAP_SendPartialObject)));
683         capabilities.setCanEditObjects(
684             static_cast<bool>(
685                 LIBMTP_Check_Capability(
686                     device.m_device,
687                     LIBMTP_DEVICECAP_EditObjects)));
688     }
689 #endif
690 
691     return capabilities;
692 }
693 
694 bool MTPDevice::listDevices(bool verbose, const std::string &dev_file)
695 {
696     int raw_devices_cnt;
697     LIBMTP_raw_device_t *raw_devices;
698 
699     // Do not output LIBMTP debug stuff
700     StreamHelper::off();
701     LIBMTP_error_number_t err = LIBMTP_Detect_Raw_Devices(
702         &raw_devices, &raw_devices_cnt);
703     StreamHelper::on();
704 
705     if (err != 0) {
706         if (err == LIBMTP_ERROR_NO_DEVICE_ATTACHED)
707             std::cerr << "No raw devices found.\n";
708         return false;
709     }
710 
711     uint8_t bnum, dnum;
712     if (!dev_file.empty() && !smtpfs_usb_devpath(dev_file, &bnum, &dnum)) {
713         std::cerr << "Can not open such device '" << dev_file << "'.\n";
714         return false;
715     }
716 
717     for (int i = 0; i < raw_devices_cnt; ++i) {
718         if (!dev_file.empty() &&
719             !(bnum == raw_devices[i].bus_location && dnum == raw_devices[i].devnum))
720             continue;
721         std::cout << i + 1 << ": "
722             << (raw_devices[i].device_entry.vendor ? raw_devices[i].device_entry.vendor : "Unknown vendor ")
723             << (raw_devices[i].device_entry.product ? raw_devices[i].device_entry.product : "Unknown product")
724             << std::endl;
725 #ifdef HAVE_LIBMTP_CHECK_CAPABILITY
726             MTPDevice dev;
727             if (verbose) {
728                 if (!dev.connect(&raw_devices[i]))
729                     return false;
730 
731                 const MTPDevice::Capabilities &cap = dev.getCapabilities();
732                 std::cout << "  - can get  partial object: " << (cap.canGetPartialObject() ? "yes" : "no") << std::endl;
733                 std::cout << "  - can send partial object: " << (cap.canSendPartialObject() ? "yes" : "no") << std::endl;
734                 std::cout << "  - can edit objects       : " << (cap.canEditObjects() ? "yes" : "no") << std::endl;
735                 dev.disconnect();
736             }
737 #endif
738     }
739     free(static_cast<void*>(raw_devices));
740 
741     return true;
742 }
743