1 /*
2  * This file Copyright (C) 2013-2014 Mnemosyne LLC
3  *
4  * It may be used under the GNU GPL versions 2 or 3
5  * or any future license endorsed by Mnemosyne LLC.
6  *
7  */
8 
9 #include <errno.h>
10 #include <stdlib.h>
11 #include <string.h>
12 
13 #include <event2/util.h> /* evutil_ascii_strcasecmp() */
14 
15 #ifndef _WIN32
16 #include <unistd.h> /* getuid() */
17 #include <sys/types.h> /* types needed by quota.h */
18 #if defined(__FreeBSD__) || defined(__OpenBSD__)
19 #include <ufs/ufs/quota.h> /* quotactl() */
20 #elif defined(__DragonFly__)
21 #include <vfs/ufs/quota.h> /* quotactl */
22 #elif defined(__NetBSD__)
23 #include <sys/param.h>
24 #ifndef statfs
25 #define statfs statvfs
26 #endif
27 #elif defined(__sun)
28 #include <sys/fs/ufs_quota.h> /* quotactl */
29 #else
30 #include <sys/quota.h> /* quotactl() */
31 #endif
32 #ifdef HAVE_GETMNTENT
33 #ifdef __sun
34 #include <fcntl.h>
35 #include <stdio.h>
36 #include <sys/mntent.h>
37 #include <sys/mnttab.h>
38 #define _PATH_MOUNTED MNTTAB
39 #else
40 #include <mntent.h>
41 #include <paths.h> /* _PATH_MOUNTED */
42 #endif
43 #else /* BSD derived systems */
44 #include <sys/param.h>
45 #include <sys/ucred.h>
46 #include <sys/mount.h>
47 #endif
48 #endif
49 
50 #ifdef __APPLE__
51 #ifndef HAVE_SYS_STATVFS_H
52 #define HAVE_SYS_STATVFS_H
53 #endif
54 #ifndef HAVE_STATVFS
55 #define HAVE_STATVFS
56 #endif
57 #endif
58 
59 #ifdef HAVE_SYS_STATVFS_H
60 #include <sys/statvfs.h>
61 #endif
62 
63 #ifdef HAVE_XFS_XFS_H
64 #define HAVE_XQM
65 #include <xfs/xqm.h>
66 #endif
67 
68 #include "transmission.h"
69 #include "utils.h"
70 #include "platform-quota.h"
71 
72 /***
73 ****
74 ***/
75 
76 #ifndef _WIN32
77 
getdev(char const * path)78 static char const* getdev(char const* path)
79 {
80 #ifdef HAVE_GETMNTENT
81 
82     FILE* fp;
83 
84 #ifdef __sun
85 
86     struct mnttab mnt;
87     fp = fopen(_PATH_MOUNTED, "r");
88 
89     if (fp == NULL)
90     {
91         return NULL;
92     }
93 
94     while (getmntent(fp, &mnt) != -1)
95     {
96         if (tr_strcmp0(path, mnt.mnt_mountp) == 0)
97         {
98             break;
99         }
100     }
101 
102     fclose(fp);
103     return mnt.mnt_special;
104 
105 #else
106 
107     struct mntent* mnt;
108 
109     fp = setmntent(_PATH_MOUNTED, "r");
110 
111     if (fp == NULL)
112     {
113         return NULL;
114     }
115 
116     while ((mnt = getmntent(fp)) != NULL)
117     {
118         if (tr_strcmp0(path, mnt->mnt_dir) == 0)
119         {
120             break;
121         }
122     }
123 
124     endmntent(fp);
125     return mnt != NULL ? mnt->mnt_fsname : NULL;
126 
127 #endif
128 
129 #else /* BSD derived systems */
130 
131     int n;
132     struct statfs* mnt;
133 
134     n = getmntinfo(&mnt, MNT_WAIT);
135 
136     if (n == 0)
137     {
138         return NULL;
139     }
140 
141     for (int i = 0; i < n; i++)
142     {
143         if (tr_strcmp0(path, mnt[i].f_mntonname) == 0)
144         {
145             return mnt[i].f_mntfromname;
146         }
147     }
148 
149     return NULL;
150 
151 #endif
152 }
153 
getfstype(char const * device)154 static char const* getfstype(char const* device)
155 {
156 #ifdef HAVE_GETMNTENT
157 
158     FILE* fp;
159 
160 #ifdef __sun
161 
162     struct mnttab mnt;
163     fp = fopen(_PATH_MOUNTED, "r");
164 
165     if (fp == NULL)
166     {
167         return NULL;
168     }
169 
170     while (getmntent(fp, &mnt) != -1)
171     {
172         if (tr_strcmp0(device, mnt.mnt_mountp) == 0)
173         {
174             break;
175         }
176     }
177 
178     fclose(fp);
179     return mnt.mnt_fstype;
180 
181 #else
182 
183     struct mntent* mnt;
184 
185     fp = setmntent(_PATH_MOUNTED, "r");
186 
187     if (fp == NULL)
188     {
189         return NULL;
190     }
191 
192     while ((mnt = getmntent(fp)) != NULL)
193     {
194         if (tr_strcmp0(device, mnt->mnt_fsname) == 0)
195         {
196             break;
197         }
198     }
199 
200     endmntent(fp);
201     return mnt != NULL ? mnt->mnt_type : NULL;
202 
203 #endif
204 
205 #else /* BSD derived systems */
206 
207     int n;
208     struct statfs* mnt;
209 
210     n = getmntinfo(&mnt, MNT_WAIT);
211 
212     if (n == 0)
213     {
214         return NULL;
215     }
216 
217     for (int i = 0; i < n; i++)
218     {
219         if (tr_strcmp0(device, mnt[i].f_mntfromname) == 0)
220         {
221             return mnt[i].f_fstypename;
222         }
223     }
224 
225     return NULL;
226 
227 #endif
228 }
229 
getblkdev(char const * path)230 static char const* getblkdev(char const* path)
231 {
232     char* c;
233     char* dir;
234     char const* device;
235 
236     dir = tr_strdup(path);
237 
238     for (;;)
239     {
240         device = getdev(dir);
241 
242         if (device != NULL)
243         {
244             break;
245         }
246 
247         c = strrchr(dir, '/');
248 
249         if (c != NULL)
250         {
251             *c = '\0';
252         }
253         else
254         {
255             break;
256         }
257     }
258 
259     tr_free(dir);
260     return device;
261 }
262 
263 #if defined(__NetBSD__) && __NetBSD_Version__ >= 600000000
264 
265 #include <quota.h>
266 
getquota(char const * device)267 static int64_t getquota(char const* device)
268 {
269     struct quotahandle* qh;
270     struct quotakey qk;
271     struct quotaval qv;
272     int64_t limit;
273     int64_t freespace;
274     int64_t spaceused;
275 
276     qh = quota_open(device);
277 
278     if (qh == NULL)
279     {
280         return -1;
281     }
282 
283     qk.qk_idtype = QUOTA_IDTYPE_USER;
284     qk.qk_id = getuid();
285     qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS;
286 
287     if (quota_get(qh, &qk, &qv) == -1)
288     {
289         quota_close(qh);
290         return -1;
291     }
292 
293     if (qv.qv_softlimit > 0)
294     {
295         limit = qv.qv_softlimit;
296     }
297     else if (qv.qv_hardlimit > 0)
298     {
299         limit = qv.qv_hardlimit;
300     }
301     else
302     {
303         quota_close(qh);
304         return -1;
305     }
306 
307     spaceused = qv.qv_usage;
308     quota_close(qh);
309 
310     freespace = limit - spaceused;
311     return freespace < 0 ? 0 : freespace;
312 }
313 
314 #else
315 
getquota(char const * device)316 static int64_t getquota(char const* device)
317 {
318 #if defined(__DragonFly__)
319     struct ufs_dqblk dq;
320 #else
321     struct dqblk dq;
322 #endif
323     int64_t limit;
324     int64_t freespace;
325     int64_t spaceused;
326 
327 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__APPLE__)
328     if (quotactl(device, QCMD(Q_GETQUOTA, USRQUOTA), getuid(), (caddr_t)&dq) == 0)
329     {
330 #elif defined(__sun)
331     struct quotctl op;
332     int fd = open(device, O_RDONLY);
333 
334     if (fd < 0)
335     {
336         return -1;
337     }
338 
339     op.op = Q_GETQUOTA;
340     op.uid = getuid();
341     op.addr = (caddr_t)&dq;
342 
343     if (ioctl(fd, Q_QUOTACTL, &op) == 0)
344     {
345         close(fd);
346 #else
347     if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), device, getuid(), (caddr_t)&dq) == 0)
348     {
349 #endif
350         if (dq.dqb_bsoftlimit > 0)
351         {
352             /* Use soft limit first */
353             limit = dq.dqb_bsoftlimit;
354         }
355         else if (dq.dqb_bhardlimit > 0)
356         {
357             limit = dq.dqb_bhardlimit;
358         }
359         else
360         {
361             /* No quota enabled for this user */
362             return -1;
363         }
364 
365 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
366         spaceused = (int64_t)dq.dqb_curblocks >> 1;
367 #elif defined(__APPLE__)
368         spaceused = (int64_t)dq.dqb_curbytes;
369 #elif defined(__UCLIBC__) && !TR_UCLIBC_CHECK_VERSION(1, 0, 18)
370         spaceused = (int64_t)btodb(dq.dqb_curblocks);
371 #elif defined(__sun) || (defined(_LINUX_QUOTA_VERSION) && _LINUX_QUOTA_VERSION < 2)
372         spaceused = (int64_t)dq.dqb_curblocks >> 1;
373 #else
374         spaceused = btodb(dq.dqb_curspace);
375 #endif
376 
377         freespace = limit - spaceused;
378 
379 #ifdef __APPLE__
380         return freespace < 0 ? 0 : freespace;
381 #else
382         return freespace < 0 ? 0 : (freespace * 1024);
383 #endif
384     }
385 
386 #if defined(__sun)
387     close(fd);
388 #endif
389 
390     /* something went wrong */
391     return -1;
392 }
393 
394 #endif
395 
396 #ifdef HAVE_XQM
397 
398 static int64_t getxfsquota(char* device)
399 {
400     int64_t limit;
401     int64_t freespace;
402     struct fs_disk_quota dq;
403 
404     if (quotactl(QCMD(Q_XGETQUOTA, USRQUOTA), device, getuid(), (caddr_t)&dq) == 0)
405     {
406         if (dq.d_blk_softlimit > 0)
407         {
408             /* Use soft limit first */
409             limit = dq.d_blk_softlimit >> 1;
410         }
411         else if (dq.d_blk_hardlimit > 0)
412         {
413             limit = dq.d_blk_hardlimit >> 1;
414         }
415         else
416         {
417             /* No quota enabled for this user */
418             return -1;
419         }
420 
421         freespace = limit - (dq.d_bcount >> 1);
422         return freespace < 0 ? 0 : (freespace * 1024);
423     }
424 
425     /* something went wrong */
426     return -1;
427 }
428 
429 #endif /* HAVE_XQM */
430 
431 #endif /* _WIN32 */
432 
433 static int64_t tr_getQuotaFreeSpace(struct tr_device_info const* info)
434 {
435     int64_t ret = -1;
436 
437 #ifndef _WIN32
438 
439     if (info->fstype != NULL && evutil_ascii_strcasecmp(info->fstype, "xfs") == 0)
440     {
441 #ifdef HAVE_XQM
442         ret = getxfsquota(info->device);
443 #endif
444     }
445     else
446     {
447         ret = getquota(info->device);
448     }
449 
450 #else /* _WIN32 */
451 
452     (void)info;
453 
454 #endif /* _WIN32 */
455 
456     return ret;
457 }
458 
459 static int64_t tr_getDiskFreeSpace(char const* path)
460 {
461 #ifdef _WIN32
462 
463     int64_t ret = -1;
464     wchar_t* wide_path;
465 
466     wide_path = tr_win32_utf8_to_native(path, -1);
467 
468     if (wide_path != NULL)
469     {
470         ULARGE_INTEGER freeBytesAvailable;
471 
472         if (GetDiskFreeSpaceExW(wide_path, &freeBytesAvailable, NULL, NULL))
473         {
474             ret = freeBytesAvailable.QuadPart;
475         }
476 
477         tr_free(wide_path);
478     }
479 
480     return ret;
481 
482 #elif defined(HAVE_STATVFS)
483 
484     struct statvfs buf;
485     return statvfs(path, &buf) ? -1 : (int64_t)buf.f_bavail * (int64_t)buf.f_frsize;
486 
487 #else
488 
489 #warning FIXME: not implemented
490 
491     return -1;
492 
493 #endif
494 }
495 
496 struct tr_device_info* tr_device_info_create(char const* path)
497 {
498     struct tr_device_info* info;
499 
500     info = tr_new0(struct tr_device_info, 1);
501     info->path = tr_strdup(path);
502 
503 #ifndef _WIN32
504     info->device = tr_strdup(getblkdev(path));
505     info->fstype = tr_strdup(getfstype(path));
506 #endif
507 
508     return info;
509 }
510 
511 void tr_device_info_free(struct tr_device_info* info)
512 {
513     if (info != NULL)
514     {
515         tr_free(info->fstype);
516         tr_free(info->device);
517         tr_free(info->path);
518         tr_free(info);
519     }
520 }
521 
522 int64_t tr_device_info_get_free_space(struct tr_device_info const* info)
523 {
524     int64_t free_space;
525 
526     if (info == NULL || info->path == NULL)
527     {
528         errno = EINVAL;
529         free_space = -1;
530     }
531     else
532     {
533         free_space = tr_getQuotaFreeSpace(info);
534 
535         if (free_space < 0)
536         {
537             free_space = tr_getDiskFreeSpace(info->path);
538         }
539     }
540 
541     return free_space;
542 }
543 
544 /***
545 ****
546 ***/
547