source: trunk/libtransmission/fdlimit.c @ 14327

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

(trunk, libt) #4160 - the slow slog to catch trunk up to mike.dld's 4160 diff continues. This step applies 4160-03b-file.patch, which replaces native file operations with the tr_sys_file_*() portability wrappers added in r14321.

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