source: trunk/libtransmission/fdlimit.c @ 14367

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

#5369: Improve file allocation error checking (initial patch by g.proskurin)

Additionally,

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