source: trunk/libtransmission/fdlimit.c @ 14382

Last change on this file since 14382 was 14382, checked in by mikedld, 8 years ago

Fix compilation on Windows

This should not affect non-Win32 platforms in any way.
As for Win32 (both MinGW and MSVC), this should hopefully allow for
unpatched compilation. Correct functioning is not yet guaranteed though.

  • Property svn:keywords set to Date Rev Author Id
File size: 13.8 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 14382 2014-12-13 15:22:39Z mikedld $
8 */
9
10#include <assert.h>
11#include <errno.h>
12#include <inttypes.h>
13#include <string.h>
14
15#ifndef _WIN32
16 #include <sys/time.h> /* getrlimit */
17 #include <sys/resource.h> /* getrlimit */
18#endif
19
20#include "transmission.h"
21#include "error.h"
22#include "error-types.h"
23#include "fdlimit.h"
24#include "file.h"
25#include "log.h"
26#include "session.h"
27#include "torrent.h" /* tr_isTorrent () */
28
29#define dbgmsg(...) \
30  do \
31    { \
32      if (tr_logGetDeepEnabled ()) \
33        tr_logAddDeep (__FILE__, __LINE__, NULL, __VA_ARGS__); \
34    } \
35  while (0)
36
37/***
38****
39****  Local Files
40****
41***/
42
43static bool
44preallocate_file_sparse (tr_sys_file_t fd, uint64_t length, tr_error ** error)
45{
46  tr_error * my_error = NULL;
47
48  if (length == 0)
49    return true;
50
51  if (tr_sys_file_preallocate (fd, length, TR_SYS_FILE_PREALLOC_SPARSE, &my_error))
52    return true;
53
54  dbgmsg ("Preallocating (sparse, normal) failed (%d): %s", my_error->code, my_error->message);
55
56  if (!TR_ERROR_IS_ENOSPC (my_error->code))
57    {
58      const char zero = '\0';
59
60      tr_error_clear (&my_error);
61
62      /* fallback: the old-style seek-and-write */
63      if (tr_sys_file_write_at (fd, &zero, 1, length - 1, NULL, &my_error) &&
64          tr_sys_file_truncate (fd, length, &my_error))
65        return true;
66
67      dbgmsg ("Preallocating (sparse, fallback) failed (%d): %s", my_error->code, my_error->message);
68    }
69
70  tr_error_propagate (error, &my_error);
71  return false;
72}
73
74static bool
75preallocate_file_full (tr_sys_file_t fd, uint64_t length, tr_error ** error)
76{
77  tr_error * my_error = NULL;
78
79  if (length == 0)
80    return true;
81
82  if (tr_sys_file_preallocate (fd, length, 0, &my_error))
83    return true;
84
85  dbgmsg ("Preallocating (full, normal) failed (%d): %s", my_error->code, my_error->message);
86
87  if (!TR_ERROR_IS_ENOSPC (my_error->code))
88    {
89      uint8_t buf[4096];
90      bool success = true;
91
92      memset (buf, 0, sizeof (buf));
93      tr_error_clear (&my_error);
94
95      /* fallback: the old-fashioned way */
96      while (success && length > 0)
97        {
98          const uint64_t thisPass = MIN (length, sizeof (buf));
99          uint64_t bytes_written;
100          success = tr_sys_file_write (fd, buf, thisPass, &bytes_written, &my_error);
101          length -= bytes_written;
102        }
103
104      if (success)
105        return true;
106
107      dbgmsg ("Preallocating (full, fallback) failed (%d): %s", my_error->code, my_error->message);
108    }
109
110  tr_error_propagate (error, &my_error);
111  return false;
112}
113
114/*****
115******
116******
117******
118*****/
119
120struct tr_cached_file
121{
122  bool is_writable;
123  tr_sys_file_t fd;
124  int torrent_id;
125  tr_file_index_t file_index;
126  time_t used_at;
127};
128
129static inline bool
130cached_file_is_open (const struct tr_cached_file * o)
131{
132  assert (o != NULL);
133
134  return o->fd != TR_BAD_SYS_FILE;
135}
136
137static void
138cached_file_close (struct tr_cached_file * o)
139{
140  assert (cached_file_is_open (o));
141
142  tr_sys_file_close (o->fd, NULL);
143  o->fd = TR_BAD_SYS_FILE;
144}
145
146/**
147 * returns 0 on success, or an errno value on failure.
148 * errno values include ENOENT if the parent folder doesn't exist,
149 * plus the errno values set by tr_sys_dir_create () and tr_sys_file_open ().
150 */
151static int
152cached_file_open (struct tr_cached_file  * o,
153                  const char             * filename,
154                  bool                     writable,
155                  tr_preallocation_mode    allocation,
156                  uint64_t                 file_size)
157{
158  int flags;
159  tr_sys_path_info info;
160  bool already_existed;
161  bool resize_needed;
162  tr_sys_file_t fd = TR_BAD_SYS_FILE;
163  tr_error * error = NULL;
164
165  /* create subfolders, if any */
166  if (writable)
167    {
168      char * dir = tr_sys_path_dirname (filename, NULL);
169      if (!tr_sys_dir_create (dir, TR_SYS_DIR_CREATE_PARENTS, 0777, &error))
170        {
171          tr_logAddError (_("Couldn't create \"%1$s\": %2$s"), dir, error->message);
172          tr_free (dir);
173          goto fail;
174        }
175      tr_free (dir);
176    }
177
178  already_existed = tr_sys_path_get_info (filename, 0, &info, NULL) && info.type == TR_SYS_PATH_IS_FILE;
179
180  /* we can't resize the file w/o write permissions */
181  resize_needed = already_existed && (file_size < info.size);
182  writable |= resize_needed;
183
184  /* open the file */
185  flags = writable ? (TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE) : 0;
186  flags |= TR_SYS_FILE_READ | TR_SYS_FILE_SEQUENTIAL;
187  fd = tr_sys_file_open (filename, flags, 0666, &error);
188
189  if (fd == TR_BAD_SYS_FILE)
190    {
191      tr_logAddError (_("Couldn't open \"%1$s\": %2$s"), filename, error->message);
192      goto fail;
193    }
194
195  if (writable && !already_existed && allocation != TR_PREALLOCATE_NONE)
196    {
197      bool success = false;
198      const char * type = NULL;
199
200      if (allocation == TR_PREALLOCATE_FULL)
201        {
202          success = preallocate_file_full (fd, file_size, &error);
203          type = _("full");
204        }
205      else if (allocation == TR_PREALLOCATE_SPARSE)
206        {
207          success = preallocate_file_sparse (fd, file_size, &error);
208          type = _("sparse");
209        }
210
211      assert (type != NULL);
212
213      if (!success)
214        {
215          tr_logAddError (_("Couldn't preallocate file \"%1$s\" (%2$s, size: %3$"PRIu64"): %4$s"),
216            filename, type, file_size, error->message);
217          goto fail;
218        }
219
220      tr_logAddDebug (_("Preallocated file \"%1$s\" (%2$s, size: %3$"PRIu64")"), filename, type, file_size);
221    }
222
223  /* If the file already exists and it's too large, truncate it.
224   * This is a fringe case that happens if a torrent's been updated
225   * and one of the updated torrent's files is smaller.
226   * http://trac.transmissionbt.com/ticket/2228
227   * https://bugs.launchpad.net/ubuntu/+source/transmission/+bug/318249
228   */
229  if (resize_needed && !tr_sys_file_truncate (fd, file_size, &error))
230    {
231      tr_logAddError (_("Couldn't truncate \"%1$s\": %2$s"), filename, error->message);
232      goto fail;
233    }
234
235  o->fd = fd;
236  return 0;
237
238fail:
239  {
240    const int err = error->code;
241    tr_error_free (error);
242
243    if (fd != TR_BAD_SYS_FILE)
244      tr_sys_file_close (fd, NULL);
245
246    return err;
247  }
248}
249
250/***
251****
252***/
253
254struct tr_fileset
255{
256  struct tr_cached_file * begin;
257  const struct tr_cached_file * end;
258};
259
260static void
261fileset_construct (struct tr_fileset * set, int n)
262{
263  struct tr_cached_file * o;
264  const struct tr_cached_file TR_CACHED_FILE_INIT = { false, TR_BAD_SYS_FILE, 0, 0, 0 };
265
266  set->begin = tr_new (struct tr_cached_file, n);
267  set->end = set->begin + n;
268
269  for (o=set->begin; o!=set->end; ++o)
270    *o = TR_CACHED_FILE_INIT;
271}
272
273static void
274fileset_close_all (struct tr_fileset * set)
275{
276  struct tr_cached_file * o;
277
278  if (set != NULL)
279    for (o=set->begin; o!=set->end; ++o)
280      if (cached_file_is_open (o))
281        cached_file_close (o);
282}
283
284static void
285fileset_destruct (struct tr_fileset * set)
286{
287  fileset_close_all (set);
288  tr_free (set->begin);
289  set->end = set->begin = NULL;
290}
291
292static void
293fileset_close_torrent (struct tr_fileset * set, int torrent_id)
294{
295  struct tr_cached_file * o;
296
297  if (set != NULL)
298    for (o=set->begin; o!=set->end; ++o)
299      if ((o->torrent_id == torrent_id) && cached_file_is_open (o))
300        cached_file_close (o);
301}
302
303static struct tr_cached_file *
304fileset_lookup (struct tr_fileset * set, int torrent_id, tr_file_index_t i)
305{
306  struct tr_cached_file * o;
307
308  if (set != NULL)
309    for (o=set->begin; o!=set->end; ++o)
310      if ((torrent_id == o->torrent_id) && (i == o->file_index) && cached_file_is_open (o))
311        return o;
312
313  return NULL;
314}
315
316static struct tr_cached_file *
317fileset_get_empty_slot (struct tr_fileset * set)
318{
319  struct tr_cached_file * cull = NULL;
320
321  if (set->begin != NULL)
322    {
323      struct tr_cached_file * o;
324
325      /* try to find an unused slot */
326      for (o=set->begin; o!=set->end; ++o)
327        if (!cached_file_is_open (o))
328          return o;
329
330      /* all slots are full... recycle the least recently used */
331      for (cull=NULL, o=set->begin; o!=set->end; ++o)
332        if (!cull || o->used_at < cull->used_at)
333          cull = o;
334
335      cached_file_close (cull);
336    }
337
338  return cull;
339}
340
341/***
342****
343****  Startup / Shutdown
344****
345***/
346
347struct tr_fdInfo
348{
349  int peerCount;
350  struct tr_fileset fileset;
351};
352
353static void
354ensureSessionFdInfoExists (tr_session * session)
355{
356  assert (tr_isSession (session));
357
358  if (session->fdInfo == NULL)
359    {
360      struct tr_fdInfo * i;
361      const int FILE_CACHE_SIZE = 32;
362
363      /* Create the local file cache */
364      i = tr_new0 (struct tr_fdInfo, 1);
365      fileset_construct (&i->fileset, FILE_CACHE_SIZE);
366      session->fdInfo = i;
367
368#ifndef _WIN32
369      /* set the open-file limit to the largest safe size wrt FD_SETSIZE */
370      struct rlimit limit;
371      if (!getrlimit (RLIMIT_NOFILE, &limit))
372        {
373          const int old_limit = (int) limit.rlim_cur;
374          const int new_limit = MIN (limit.rlim_max, FD_SETSIZE);
375          if (new_limit != old_limit)
376            {
377              limit.rlim_cur = new_limit;
378              setrlimit (RLIMIT_NOFILE, &limit);
379              getrlimit (RLIMIT_NOFILE, &limit);
380              tr_logAddInfo ("Changed open file limit from %d to %d", old_limit, (int)limit.rlim_cur);
381            }
382        }
383#endif
384    }
385}
386
387void
388tr_fdClose (tr_session * session)
389{
390  if (session && session->fdInfo)
391    {
392      struct tr_fdInfo * i = session->fdInfo;
393      fileset_destruct (&i->fileset);
394      tr_free (i);
395      session->fdInfo = NULL;
396    }
397}
398
399/***
400****
401***/
402
403static struct tr_fileset*
404get_fileset (tr_session * session)
405{
406  if (!session)
407    return NULL;
408
409  ensureSessionFdInfoExists (session);
410  return &session->fdInfo->fileset;
411}
412
413void
414tr_fdFileClose (tr_session * s, const tr_torrent * tor, tr_file_index_t i)
415{
416  struct tr_cached_file * o;
417
418  if ((o = fileset_lookup (get_fileset (s), tr_torrentId (tor), i)))
419    {
420      /* flush writable files so that their mtimes will be
421       * up-to-date when this function returns to the caller... */
422      if (o->is_writable)
423        tr_sys_file_flush (o->fd, NULL);
424
425      cached_file_close (o);
426    }
427}
428
429tr_sys_file_t
430tr_fdFileGetCached (tr_session * s, int torrent_id, tr_file_index_t i, bool writable)
431{
432  struct tr_cached_file * o = fileset_lookup (get_fileset (s), torrent_id, i);
433
434  if (!o || (writable && !o->is_writable))
435    return TR_BAD_SYS_FILE;
436
437  o->used_at = tr_time ();
438  return o->fd;
439}
440
441bool
442tr_fdFileGetCachedMTime (tr_session * s, int torrent_id, tr_file_index_t i, time_t * mtime)
443{
444  bool success;
445  tr_sys_path_info info;
446  struct tr_cached_file * o = fileset_lookup (get_fileset (s), torrent_id, i);
447
448  if ((success = (o != NULL) && tr_sys_file_get_info (o->fd, &info, NULL)))
449    *mtime = info.last_modified_at;
450
451  return success;
452}
453
454void
455tr_fdTorrentClose (tr_session * session, int torrent_id)
456{
457  assert (tr_sessionIsLocked (session));
458
459  fileset_close_torrent (get_fileset (session), torrent_id);
460}
461
462/* returns an fd on success, or a TR_BAD_SYS_FILE on failure and sets errno */
463tr_sys_file_t
464tr_fdFileCheckout (tr_session             * session,
465                   int                      torrent_id,
466                   tr_file_index_t          i,
467                   const char             * filename,
468                   bool                     writable,
469                   tr_preallocation_mode    allocation,
470                   uint64_t                 file_size)
471{
472  struct tr_fileset * set = get_fileset (session);
473  struct tr_cached_file * o = fileset_lookup (set, torrent_id, i);
474
475  if (o && writable && !o->is_writable)
476    cached_file_close (o); /* close it so we can reopen in rw mode */
477  else if (!o)
478    o = fileset_get_empty_slot (set);
479
480  if (!cached_file_is_open (o))
481    {
482      const int err = cached_file_open (o, filename, writable, allocation, file_size);
483      if (err)
484        {
485          errno = err;
486          return TR_BAD_SYS_FILE;
487        }
488
489      dbgmsg ("opened '%s' writable %c", filename, writable?'y':'n');
490      o->is_writable = writable;
491    }
492
493  dbgmsg ("checking out '%s'", filename);
494  o->torrent_id = torrent_id;
495  o->file_index = i;
496  o->used_at = tr_time ();
497  return o->fd;
498}
499
500/***
501****
502****  Sockets
503****
504***/
505
506int
507tr_fdSocketCreate (tr_session * session, int domain, int type)
508{
509  int s = -1;
510  struct tr_fdInfo * gFd;
511  assert (tr_isSession (session));
512
513  ensureSessionFdInfoExists (session);
514  gFd = session->fdInfo;
515
516  if (gFd->peerCount < session->peerLimit)
517    if ((s = socket (domain, type, 0)) < 0)
518      if (sockerrno != EAFNOSUPPORT)
519        tr_logAddError (_("Couldn't create socket: %s"), tr_strerror (sockerrno));
520
521  if (s > -1)
522    ++gFd->peerCount;
523
524  assert (gFd->peerCount >= 0);
525
526  if (s >= 0)
527    {
528      static bool buf_logged = false;
529      if (!buf_logged)
530        {
531          int i;
532          socklen_t size = sizeof (int);
533          buf_logged = true;
534          getsockopt (s, SOL_SOCKET, SO_SNDBUF, &i, &size);
535          tr_logAddDebug ("SO_SNDBUF size is %d", i);
536          getsockopt (s, SOL_SOCKET, SO_RCVBUF, &i, &size);
537          tr_logAddDebug ("SO_RCVBUF size is %d", i);
538        }
539    }
540
541  return s;
542}
543
544int
545tr_fdSocketAccept (tr_session * s, int sockfd, tr_address * addr, tr_port * port)
546{
547  int fd;
548  socklen_t len;
549  struct tr_fdInfo * gFd;
550  struct sockaddr_storage sock;
551
552  assert (tr_isSession (s));
553  assert (addr);
554  assert (port);
555
556  ensureSessionFdInfoExists (s);
557  gFd = s->fdInfo;
558
559  len = sizeof (struct sockaddr_storage);
560  fd = accept (sockfd, (struct sockaddr *) &sock, &len);
561
562  if (fd >= 0)
563    {
564      if ((gFd->peerCount < s->peerLimit)
565          && tr_address_from_sockaddr_storage (addr, port, &sock))
566        {
567          ++gFd->peerCount;
568        }
569        else
570        {
571          tr_netCloseSocket (fd);
572          fd = -1;
573        }
574    }
575
576  return fd;
577}
578
579void
580tr_fdSocketClose (tr_session * session, int fd)
581{
582  assert (tr_isSession (session));
583
584  if (session->fdInfo != NULL)
585    {
586      struct tr_fdInfo * gFd = session->fdInfo;
587
588      if (fd >= 0)
589        {
590          tr_netCloseSocket (fd);
591          --gFd->peerCount;
592        }
593
594      assert (gFd->peerCount >= 0);
595    }
596}
Note: See TracBrowser for help on using the repository browser.