source: trunk/libtransmission/fdlimit.c @ 14320

Last change on this file since 14320 was 14320, checked in by jordan, 8 years ago

(trunk, libT) #4160 'foreign character support' -- merge mike.dld's 4160-02b-path.patch, which updates the codebase to use the new tr_sys_path_*() portability wrappers introduced in 4160-02a

  • Property svn:keywords set to Date Rev Author Id
File size: 16.9 KB
Line 
1/*
2 * This file Copyright (C) 2005-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 * $Id: fdlimit.c 14320 2014-07-08 00:08:43Z jordan $
8 */
9
10#ifdef HAVE_POSIX_FADVISE
11 #ifdef _XOPEN_SOURCE
12  #undef _XOPEN_SOURCE
13 #endif
14 #define _XOPEN_SOURCE 600
15#endif
16
17#include <assert.h>
18#include <errno.h>
19#include <inttypes.h>
20#include <string.h>
21#ifdef __APPLE__
22 #include <fcntl.h>
23#endif
24
25#ifdef HAVE_FALLOCATE64
26  /* FIXME can't find the right #include voodoo to pick up the declaration.. */
27  extern int fallocate64 (int fd, int mode, uint64_t offset, uint64_t len);
28#endif
29
30#ifdef HAVE_XFS_XFS_H
31 #include <xfs/xfs.h>
32#endif
33
34#include <sys/types.h>
35#include <sys/stat.h>
36#include <sys/time.h> /* getrlimit */
37#include <sys/resource.h> /* getrlimit */
38#include <fcntl.h> /* O_LARGEFILE posix_fadvise */
39#include <unistd.h> /* lseek (), write (), ftruncate (), pread (), pwrite (), etc */
40
41#include "transmission.h"
42#include "fdlimit.h"
43#include "file.h"
44#include "log.h"
45#include "session.h"
46#include "torrent.h" /* tr_isTorrent () */
47
48#define dbgmsg(...) \
49  do \
50    { \
51      if (tr_logGetDeepEnabled ()) \
52        tr_logAddDeep (__FILE__, __LINE__, NULL, __VA_ARGS__); \
53    } \
54  while (0)
55
56/***
57****
58****  Local Files
59****
60***/
61
62#ifndef O_LARGEFILE
63 #define O_LARGEFILE 0
64#endif
65
66#ifndef O_BINARY
67 #define O_BINARY 0
68#endif
69
70#ifndef O_SEQUENTIAL
71 #define O_SEQUENTIAL 0
72#endif
73
74
75static bool
76preallocate_file_sparse (int fd, uint64_t length)
77{
78  const char zero = '\0';
79  bool success = 0;
80
81  if (!length)
82    success = true;
83
84#ifdef HAVE_FALLOCATE64
85  if (!success) /* fallocate64 is always preferred, so try it first */
86    success = !fallocate64 (fd, 0, 0, length);
87#endif
88
89  if (!success) /* fallback: the old-style seek-and-write */
90    success = (lseek (fd, length-1, SEEK_SET) != -1)
91           && (write (fd, &zero, 1) != -1)
92           && (ftruncate (fd, length) != -1);
93
94  return success;
95}
96
97static bool
98preallocate_file_full (const char * filename, uint64_t length)
99{
100  bool success = 0;
101
102#ifdef _WIN32
103
104  HANDLE hFile = CreateFile (filename, GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_FLAG_RANDOM_ACCESS, 0);
105  if (hFile != INVALID_HANDLE_VALUE)
106    {
107      LARGE_INTEGER li;
108      li.QuadPart = length;
109      success = SetFilePointerEx (hFile, li, NULL, FILE_BEGIN) && SetEndOfFile (hFile);
110      CloseHandle (hFile);
111    }
112
113#else
114
115  int flags = O_RDWR | O_CREAT | O_LARGEFILE;
116  int fd = open (filename, flags, 0666);
117  if (fd >= 0)
118    {
119# ifdef HAVE_FALLOCATE64
120      if (!success)
121        success = !fallocate64 (fd, 0, 0, length);
122# endif
123# ifdef HAVE_XFS_XFS_H
124      if (!success && platform_test_xfs_fd (fd))
125        {
126          xfs_flock64_t fl;
127          fl.l_whence = 0;
128          fl.l_start = 0;
129          fl.l_len = length;
130          success = !xfsctl (NULL, fd, XFS_IOC_RESVSP64, &fl);
131        }
132# endif
133# ifdef __APPLE__
134      if (!success)
135        {
136          fstore_t fst;
137          fst.fst_flags = F_ALLOCATECONTIG;
138          fst.fst_posmode = F_PEOFPOSMODE;
139          fst.fst_offset = 0;
140          fst.fst_length = length;
141          fst.fst_bytesalloc = 0;
142          success = !fcntl (fd, F_PREALLOCATE, &fst);
143        }
144# endif
145# ifdef HAVE_POSIX_FALLOCATE
146      if (!success)
147        success = !posix_fallocate (fd, 0, length);
148# endif
149
150      if (!success) /* if nothing else works, do it the old-fashioned way */
151        {
152          uint8_t buf[ 4096 ];
153          memset (buf, 0, sizeof (buf));
154          success = true;
155          while (success && (length > 0))
156            {
157              const int thisPass = MIN (length, sizeof (buf));
158              success = write (fd, buf, thisPass) == thisPass;
159              length -= thisPass;
160            }
161        }
162
163      close (fd);
164    }
165
166#endif
167
168  return success;
169}
170
171
172/* portability wrapper for fsync (). */
173int
174tr_fsync (int fd)
175{
176#ifdef _WIN32
177  return _commit (fd);
178#else
179  return fsync (fd);
180#endif
181}
182
183
184/* Like pread and pwrite, except that the position is undefined afterwards.
185   And of course they are not thread-safe. */
186
187/* don't use pread/pwrite on old versions of uClibc because they're buggy.
188 * https://trac.transmissionbt.com/ticket/3826 */
189#ifdef __UCLIBC__
190#define TR_UCLIBC_CHECK_VERSION(major,minor,micro) \
191  (__UCLIBC_MAJOR__ > (major) || \
192   (__UCLIBC_MAJOR__ == (major) && __UCLIBC_MINOR__ > (minor)) || \
193   (__UCLIBC_MAJOR__ == (major) && __UCLIBC_MINOR__ == (minor) && \
194      __UCLIBC_SUBLEVEL__ >= (micro)))
195#if !TR_UCLIBC_CHECK_VERSION (0,9,28)
196 #undef HAVE_PREAD
197 #undef HAVE_PWRITE
198#endif
199#endif
200
201#ifdef __APPLE__
202 #define HAVE_PREAD
203 #define HAVE_PWRITE
204#endif
205
206ssize_t
207tr_pread (int fd, void *buf, size_t count, off_t offset)
208{
209#ifdef HAVE_PREAD
210  return pread (fd, buf, count, offset);
211#else
212  const off_t lrc = lseek (fd, offset, SEEK_SET);
213  if (lrc < 0)
214    return -1;
215  return read (fd, buf, count);
216#endif
217}
218
219ssize_t
220tr_pwrite (int fd, const void *buf, size_t count, off_t offset)
221{
222#ifdef HAVE_PWRITE
223  return pwrite (fd, buf, count, offset);
224#else
225  const off_t lrc = lseek (fd, offset, SEEK_SET);
226  if (lrc < 0)
227    return -1;
228  return write (fd, buf, count);
229#endif
230}
231
232int
233tr_prefetch (int fd UNUSED, off_t offset UNUSED, size_t count UNUSED)
234{
235#ifdef HAVE_POSIX_FADVISE
236  return posix_fadvise (fd, offset, count, POSIX_FADV_WILLNEED);
237#elif defined (__APPLE__)
238  struct radvisory radv;
239  radv.ra_offset = offset;
240  radv.ra_count = count;
241  return fcntl (fd, F_RDADVISE, &radv);
242#else
243  return 0;
244#endif
245}
246
247void
248tr_set_file_for_single_pass (int fd)
249{
250  if (fd >= 0)
251    {
252      /* Set hints about the lookahead buffer and caching. It's okay
253         for these to fail silently, so don't let them affect errno */
254      const int err = errno;
255#ifdef HAVE_POSIX_FADVISE
256      posix_fadvise (fd, 0, 0, POSIX_FADV_SEQUENTIAL);
257#endif
258#ifdef __APPLE__
259      fcntl (fd, F_RDAHEAD, 1);
260      fcntl (fd, F_NOCACHE, 1);
261#endif
262      errno = err;
263    }
264}
265
266static int
267open_local_file (const char * filename, int flags)
268{
269  const int fd = open (filename, flags, 0666);
270  tr_set_file_for_single_pass (fd);
271  return fd;
272}
273int
274tr_open_file_for_writing (const char * filename)
275{
276  return open_local_file (filename, O_LARGEFILE|O_BINARY|O_CREAT|O_WRONLY);
277}
278int
279tr_open_file_for_scanning (const char * filename)
280{
281  return open_local_file (filename, O_LARGEFILE|O_BINARY|O_SEQUENTIAL|O_RDONLY);
282}
283
284void
285tr_close_file (int fd)
286{
287  close (fd);
288}
289
290/*****
291******
292******
293******
294*****/
295
296struct tr_cached_file
297{
298  bool is_writable;
299  int fd;
300  int torrent_id;
301  tr_file_index_t file_index;
302  time_t used_at;
303};
304
305static inline bool
306cached_file_is_open (const struct tr_cached_file * o)
307{
308  assert (o != NULL);
309
310  return o->fd >= 0;
311}
312
313static void
314cached_file_close (struct tr_cached_file * o)
315{
316  assert (cached_file_is_open (o));
317
318  tr_close_file (o->fd);
319  o->fd = -1;
320}
321
322/**
323 * returns 0 on success, or an errno value on failure.
324 * errno values include ENOENT if the parent folder doesn't exist,
325 * plus the errno values set by tr_mkdirp () and open ().
326 */
327static int
328cached_file_open (struct tr_cached_file  * o,
329                  const char             * filename,
330                  bool                     writable,
331                  tr_preallocation_mode    allocation,
332                  uint64_t                 file_size)
333{
334  int flags;
335  tr_sys_path_info info;
336  bool already_existed;
337  bool resize_needed;
338
339  /* create subfolders, if any */
340  if (writable)
341    {
342      char * dir = tr_sys_path_dirname (filename, NULL);
343      const int err = tr_mkdirp (dir, 0777) ? errno : 0;
344      if (err)
345        {
346          tr_logAddError (_("Couldn't create \"%1$s\": %2$s"), dir, tr_strerror (err));
347          tr_free (dir);
348          return err;
349        }
350      tr_free (dir);
351    }
352
353  already_existed = tr_sys_path_get_info (filename, 0, &info, NULL) && info.type == TR_SYS_PATH_IS_FILE;
354
355  if (writable && !already_existed && (allocation == TR_PREALLOCATE_FULL))
356    if (preallocate_file_full (filename, file_size))
357      tr_logAddDebug ("Preallocated file \"%s\"", filename);
358
359  /* we can't resize the file w/o write permissions */
360  resize_needed = already_existed && (file_size < info.size);
361  writable |= resize_needed;
362
363  /* open the file */
364  flags = writable ? (O_RDWR | O_CREAT) : O_RDONLY;
365  flags |= O_LARGEFILE | O_BINARY | O_SEQUENTIAL;
366  o->fd = open (filename, flags, 0666);
367
368  if (o->fd == -1)
369    {
370      const int err = errno;
371      tr_logAddError (_("Couldn't open \"%1$s\": %2$s"), filename, tr_strerror (err));
372      return err;
373    }
374
375  /* If the file already exists and it's too large, truncate it.
376   * This is a fringe case that happens if a torrent's been updated
377   * and one of the updated torrent's files is smaller.
378   * http://trac.transmissionbt.com/ticket/2228
379   * https://bugs.launchpad.net/ubuntu/+source/transmission/+bug/318249
380   */
381  if (resize_needed && (ftruncate (o->fd, file_size) == -1))
382    {
383      const int err = errno;
384      tr_logAddError (_("Couldn't truncate \"%1$s\": %2$s"), filename, tr_strerror (err));
385      return err;
386    }
387
388  if (writable && !already_existed && (allocation == TR_PREALLOCATE_SPARSE))
389    preallocate_file_sparse (o->fd, file_size);
390
391  /* Many (most?) clients request blocks in ascending order,
392   * so increase the readahead buffer.
393   * Also, disable OS-level caching because "inactive memory" angers users. */
394  tr_set_file_for_single_pass (o->fd);
395
396  return 0;
397}
398
399/***
400****
401***/
402
403struct tr_fileset
404{
405  struct tr_cached_file * begin;
406  const struct tr_cached_file * end;
407};
408
409static void
410fileset_construct (struct tr_fileset * set, int n)
411{
412  struct tr_cached_file * o;
413  const struct tr_cached_file TR_CACHED_FILE_INIT = { 0, -1, 0, 0, 0 };
414
415  set->begin = tr_new (struct tr_cached_file, n);
416  set->end = set->begin + n;
417
418  for (o=set->begin; o!=set->end; ++o)
419    *o = TR_CACHED_FILE_INIT;
420}
421
422static void
423fileset_close_all (struct tr_fileset * set)
424{
425  struct tr_cached_file * o;
426
427  if (set != NULL)
428    for (o=set->begin; o!=set->end; ++o)
429      if (cached_file_is_open (o))
430        cached_file_close (o);
431}
432
433static void
434fileset_destruct (struct tr_fileset * set)
435{
436  fileset_close_all (set);
437  tr_free (set->begin);
438  set->end = set->begin = NULL;
439}
440
441static void
442fileset_close_torrent (struct tr_fileset * set, int torrent_id)
443{
444  struct tr_cached_file * o;
445
446  if (set != NULL)
447    for (o=set->begin; o!=set->end; ++o)
448      if ((o->torrent_id == torrent_id) && cached_file_is_open (o))
449        cached_file_close (o);
450}
451
452static struct tr_cached_file *
453fileset_lookup (struct tr_fileset * set, int torrent_id, tr_file_index_t i)
454{
455  struct tr_cached_file * o;
456
457  if (set != NULL)
458    for (o=set->begin; o!=set->end; ++o)
459      if ((torrent_id == o->torrent_id) && (i == o->file_index) && cached_file_is_open (o))
460        return o;
461
462  return NULL;
463}
464
465static struct tr_cached_file *
466fileset_get_empty_slot (struct tr_fileset * set)
467{
468  struct tr_cached_file * cull = NULL;
469
470  if (set->begin != NULL)
471    {
472      struct tr_cached_file * o;
473
474      /* try to find an unused slot */
475      for (o=set->begin; o!=set->end; ++o)
476        if (!cached_file_is_open (o))
477          return o;
478
479      /* all slots are full... recycle the least recently used */
480      for (cull=NULL, o=set->begin; o!=set->end; ++o)
481        if (!cull || o->used_at < cull->used_at)
482          cull = o;
483
484      cached_file_close (cull);
485    }
486
487  return cull;
488}
489
490/***
491****
492****  Startup / Shutdown
493****
494***/
495
496struct tr_fdInfo
497{
498  int peerCount;
499  struct tr_fileset fileset;
500};
501
502static void
503ensureSessionFdInfoExists (tr_session * session)
504{
505  assert (tr_isSession (session));
506
507  if (session->fdInfo == NULL)
508    {
509      struct rlimit limit;
510      struct tr_fdInfo * i;
511      const int FILE_CACHE_SIZE = 32;
512
513      /* Create the local file cache */
514      i = tr_new0 (struct tr_fdInfo, 1);
515      fileset_construct (&i->fileset, FILE_CACHE_SIZE);
516      session->fdInfo = i;
517
518      /* set the open-file limit to the largest safe size wrt FD_SETSIZE */
519      if (!getrlimit (RLIMIT_NOFILE, &limit))
520        {
521          const int old_limit = (int) limit.rlim_cur;
522          const int new_limit = MIN (limit.rlim_max, FD_SETSIZE);
523          if (new_limit != old_limit)
524            {
525              limit.rlim_cur = new_limit;
526              setrlimit (RLIMIT_NOFILE, &limit);
527              getrlimit (RLIMIT_NOFILE, &limit);
528              tr_logAddInfo ("Changed open file limit from %d to %d", old_limit, (int)limit.rlim_cur);
529            }
530        }
531    }
532}
533
534void
535tr_fdClose (tr_session * session)
536{
537  if (session && session->fdInfo)
538    {
539      struct tr_fdInfo * i = session->fdInfo;
540      fileset_destruct (&i->fileset);
541      tr_free (i);
542      session->fdInfo = NULL;
543    }
544}
545
546/***
547****
548***/
549
550static struct tr_fileset*
551get_fileset (tr_session * session)
552{
553  if (!session)
554    return NULL;
555
556  ensureSessionFdInfoExists (session);
557  return &session->fdInfo->fileset;
558}
559
560void
561tr_fdFileClose (tr_session * s, const tr_torrent * tor, tr_file_index_t i)
562{
563  struct tr_cached_file * o;
564
565  if ((o = fileset_lookup (get_fileset (s), tr_torrentId (tor), i)))
566    {
567      /* flush writable files so that their mtimes will be
568       * up-to-date when this function returns to the caller... */
569      if (o->is_writable)
570        tr_fsync (o->fd);
571
572      cached_file_close (o);
573    }
574}
575
576int
577tr_fdFileGetCached (tr_session * s, int torrent_id, tr_file_index_t i, bool writable)
578{
579  struct tr_cached_file * o = fileset_lookup (get_fileset (s), torrent_id, i);
580
581  if (!o || (writable && !o->is_writable))
582    return -1;
583
584  o->used_at = tr_time ();
585  return o->fd;
586}
587
588#ifdef __APPLE__
589 #define TR_STAT_MTIME(sb)((sb).st_mtimespec.tv_sec)
590#else
591 #define TR_STAT_MTIME(sb)((sb).st_mtime)
592#endif
593
594bool
595tr_fdFileGetCachedMTime (tr_session * s, int torrent_id, tr_file_index_t i, time_t * mtime)
596{
597  bool success;
598  struct stat sb;
599  struct tr_cached_file * o = fileset_lookup (get_fileset (s), torrent_id, i);
600
601  if ((success = (o != NULL) && !fstat (o->fd, &sb)))
602    *mtime = TR_STAT_MTIME (sb);
603
604  return success;
605}
606
607void
608tr_fdTorrentClose (tr_session * session, int torrent_id)
609{
610  assert (tr_sessionIsLocked (session));
611
612  fileset_close_torrent (get_fileset (session), torrent_id);
613}
614
615/* returns an fd on success, or a -1 on failure and sets errno */
616int
617tr_fdFileCheckout (tr_session             * session,
618                   int                      torrent_id,
619                   tr_file_index_t          i,
620                   const char             * filename,
621                   bool                     writable,
622                   tr_preallocation_mode    allocation,
623                   uint64_t                 file_size)
624{
625  struct tr_fileset * set = get_fileset (session);
626  struct tr_cached_file * o = fileset_lookup (set, torrent_id, i);
627
628  if (o && writable && !o->is_writable)
629    cached_file_close (o); /* close it so we can reopen in rw mode */
630  else if (!o)
631    o = fileset_get_empty_slot (set);
632
633  if (!cached_file_is_open (o))
634    {
635      const int err = cached_file_open (o, filename, writable, allocation, file_size);
636      if (err)
637        {
638          errno = err;
639          return -1;
640        }
641
642      dbgmsg ("opened '%s' writable %c", filename, writable?'y':'n');
643      o->is_writable = writable;
644    }
645
646  dbgmsg ("checking out '%s'", filename);
647  o->torrent_id = torrent_id;
648  o->file_index = i;
649  o->used_at = tr_time ();
650  return o->fd;
651}
652
653/***
654****
655****  Sockets
656****
657***/
658
659int
660tr_fdSocketCreate (tr_session * session, int domain, int type)
661{
662  int s = -1;
663  struct tr_fdInfo * gFd;
664  assert (tr_isSession (session));
665
666  ensureSessionFdInfoExists (session);
667  gFd = session->fdInfo;
668
669  if (gFd->peerCount < session->peerLimit)
670    if ((s = socket (domain, type, 0)) < 0)
671      if (sockerrno != EAFNOSUPPORT)
672        tr_logAddError (_("Couldn't create socket: %s"), tr_strerror (sockerrno));
673
674  if (s > -1)
675    ++gFd->peerCount;
676
677  assert (gFd->peerCount >= 0);
678
679  if (s >= 0)
680    {
681      static bool buf_logged = false;
682      if (!buf_logged)
683        {
684          int i;
685          socklen_t size = sizeof (int);
686          buf_logged = true;
687          getsockopt (s, SOL_SOCKET, SO_SNDBUF, &i, &size);
688          tr_logAddDebug ("SO_SNDBUF size is %d", i);
689          getsockopt (s, SOL_SOCKET, SO_RCVBUF, &i, &size);
690          tr_logAddDebug ("SO_RCVBUF size is %d", i);
691        }
692    }
693
694  return s;
695}
696
697int
698tr_fdSocketAccept (tr_session * s, int sockfd, tr_address * addr, tr_port * port)
699{
700  int fd;
701  unsigned int len;
702  struct tr_fdInfo * gFd;
703  struct sockaddr_storage sock;
704
705  assert (tr_isSession (s));
706  assert (addr);
707  assert (port);
708
709  ensureSessionFdInfoExists (s);
710  gFd = s->fdInfo;
711
712  len = sizeof (struct sockaddr_storage);
713  fd = accept (sockfd, (struct sockaddr *) &sock, &len);
714
715  if (fd >= 0)
716    {
717      if ((gFd->peerCount < s->peerLimit)
718          && tr_address_from_sockaddr_storage (addr, port, &sock))
719        {
720          ++gFd->peerCount;
721        }
722        else
723        {
724          tr_netCloseSocket (fd);
725          fd = -1;
726        }
727    }
728
729  return fd;
730}
731
732void
733tr_fdSocketClose (tr_session * session, int fd)
734{
735  assert (tr_isSession (session));
736
737  if (session->fdInfo != NULL)
738    {
739      struct tr_fdInfo * gFd = session->fdInfo;
740
741      if (fd >= 0)
742        {
743          tr_netCloseSocket (fd);
744          --gFd->peerCount;
745        }
746
747      assert (gFd->peerCount >= 0);
748    }
749}
Note: See TracBrowser for help on using the repository browser.