source: trunk/libtransmission/fdlimit.c

Last change on this file was 14717, checked in by mikedld, 5 years ago

Handle potential dirname/basename errors where needed

  • Property svn:keywords set to Date Rev Author Id
File size: 14.4 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 14717 2016-03-13 10:41:52Z 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, &error);
169      if (dir == NULL)
170        {
171          tr_logAddError (_("Couldn't get directory for \"%1$s\": %2$s"), filename, error->message);
172          goto fail;
173        }
174      if (!tr_sys_dir_create (dir, TR_SYS_DIR_CREATE_PARENTS, 0777, &error))
175        {
176          tr_logAddError (_("Couldn't create \"%1$s\": %2$s"), dir, error->message);
177          tr_free (dir);
178          goto fail;
179        }
180      tr_free (dir);
181    }
182
183  already_existed = tr_sys_path_get_info (filename, 0, &info, NULL) && info.type == TR_SYS_PATH_IS_FILE;
184
185  /* we can't resize the file w/o write permissions */
186  resize_needed = already_existed && (file_size < info.size);
187  writable |= resize_needed;
188
189  /* open the file */
190  flags = writable ? (TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE) : 0;
191  flags |= TR_SYS_FILE_READ | TR_SYS_FILE_SEQUENTIAL;
192  fd = tr_sys_file_open (filename, flags, 0666, &error);
193
194  if (fd == TR_BAD_SYS_FILE)
195    {
196      tr_logAddError (_("Couldn't open \"%1$s\": %2$s"), filename, error->message);
197      goto fail;
198    }
199
200  if (writable && !already_existed && allocation != TR_PREALLOCATE_NONE)
201    {
202      bool success = false;
203      const char * type = NULL;
204
205      if (allocation == TR_PREALLOCATE_FULL)
206        {
207          success = preallocate_file_full (fd, file_size, &error);
208          type = _("full");
209        }
210      else if (allocation == TR_PREALLOCATE_SPARSE)
211        {
212          success = preallocate_file_sparse (fd, file_size, &error);
213          type = _("sparse");
214        }
215
216      assert (type != NULL);
217
218      if (!success)
219        {
220          tr_logAddError (_("Couldn't preallocate file \"%1$s\" (%2$s, size: %3$"PRIu64"): %4$s"),
221            filename, type, file_size, error->message);
222          goto fail;
223        }
224
225      tr_logAddDebug (_("Preallocated file \"%1$s\" (%2$s, size: %3$"PRIu64")"), filename, type, file_size);
226    }
227
228  /* If the file already exists and it's too large, truncate it.
229   * This is a fringe case that happens if a torrent's been updated
230   * and one of the updated torrent's files is smaller.
231   * http://trac.transmissionbt.com/ticket/2228
232   * https://bugs.launchpad.net/ubuntu/+source/transmission/+bug/318249
233   */
234  if (resize_needed && !tr_sys_file_truncate (fd, file_size, &error))
235    {
236      tr_logAddError (_("Couldn't truncate \"%1$s\": %2$s"), filename, error->message);
237      goto fail;
238    }
239
240  o->fd = fd;
241  return 0;
242
243fail:
244  {
245    const int err = error->code;
246    tr_error_free (error);
247
248    if (fd != TR_BAD_SYS_FILE)
249      tr_sys_file_close (fd, NULL);
250
251    return err;
252  }
253}
254
255/***
256****
257***/
258
259struct tr_fileset
260{
261  struct tr_cached_file * begin;
262  const struct tr_cached_file * end;
263};
264
265static void
266fileset_construct (struct tr_fileset * set, int n)
267{
268  struct tr_cached_file * o;
269  const struct tr_cached_file TR_CACHED_FILE_INIT = { false, TR_BAD_SYS_FILE, 0, 0, 0 };
270
271  set->begin = tr_new (struct tr_cached_file, n);
272  set->end = set->begin + n;
273
274  for (o=set->begin; o!=set->end; ++o)
275    *o = TR_CACHED_FILE_INIT;
276}
277
278static void
279fileset_close_all (struct tr_fileset * set)
280{
281  struct tr_cached_file * o;
282
283  if (set != NULL)
284    for (o=set->begin; o!=set->end; ++o)
285      if (cached_file_is_open (o))
286        cached_file_close (o);
287}
288
289static void
290fileset_destruct (struct tr_fileset * set)
291{
292  fileset_close_all (set);
293  tr_free (set->begin);
294  set->end = set->begin = NULL;
295}
296
297static void
298fileset_close_torrent (struct tr_fileset * set, int torrent_id)
299{
300  struct tr_cached_file * o;
301
302  if (set != NULL)
303    for (o=set->begin; o!=set->end; ++o)
304      if ((o->torrent_id == torrent_id) && cached_file_is_open (o))
305        cached_file_close (o);
306}
307
308static struct tr_cached_file *
309fileset_lookup (struct tr_fileset * set, int torrent_id, tr_file_index_t i)
310{
311  struct tr_cached_file * o;
312
313  if (set != NULL)
314    for (o=set->begin; o!=set->end; ++o)
315      if ((torrent_id == o->torrent_id) && (i == o->file_index) && cached_file_is_open (o))
316        return o;
317
318  return NULL;
319}
320
321static struct tr_cached_file *
322fileset_get_empty_slot (struct tr_fileset * set)
323{
324  struct tr_cached_file * cull = NULL;
325
326  if (set->begin != NULL)
327    {
328      struct tr_cached_file * o;
329
330      /* try to find an unused slot */
331      for (o=set->begin; o!=set->end; ++o)
332        if (!cached_file_is_open (o))
333          return o;
334
335      /* all slots are full... recycle the least recently used */
336      for (cull=NULL, o=set->begin; o!=set->end; ++o)
337        if (!cull || o->used_at < cull->used_at)
338          cull = o;
339
340      cached_file_close (cull);
341    }
342
343  return cull;
344}
345
346/***
347****
348****  Startup / Shutdown
349****
350***/
351
352struct tr_fdInfo
353{
354  int peerCount;
355  struct tr_fileset fileset;
356};
357
358static void
359ensureSessionFdInfoExists (tr_session * session)
360{
361  assert (tr_isSession (session));
362
363  if (session->fdInfo == NULL)
364    {
365      struct tr_fdInfo * i;
366      const int FILE_CACHE_SIZE = 32;
367
368      /* Create the local file cache */
369      i = tr_new0 (struct tr_fdInfo, 1);
370      fileset_construct (&i->fileset, FILE_CACHE_SIZE);
371      session->fdInfo = i;
372
373#ifndef _WIN32
374      /* set the open-file limit to the largest safe size wrt FD_SETSIZE */
375      struct rlimit limit;
376      if (!getrlimit (RLIMIT_NOFILE, &limit))
377        {
378          const int old_limit = (int) limit.rlim_cur;
379          const int new_limit = MIN (limit.rlim_max, FD_SETSIZE);
380          if (new_limit != old_limit)
381            {
382              limit.rlim_cur = new_limit;
383              setrlimit (RLIMIT_NOFILE, &limit);
384              getrlimit (RLIMIT_NOFILE, &limit);
385              tr_logAddInfo ("Changed open file limit from %d to %d", old_limit, (int)limit.rlim_cur);
386            }
387        }
388#endif
389    }
390}
391
392void
393tr_fdClose (tr_session * session)
394{
395  if (session && session->fdInfo)
396    {
397      struct tr_fdInfo * i = session->fdInfo;
398      fileset_destruct (&i->fileset);
399      tr_free (i);
400      session->fdInfo = NULL;
401    }
402}
403
404/***
405****
406***/
407
408static struct tr_fileset*
409get_fileset (tr_session * session)
410{
411  if (!session)
412    return NULL;
413
414  ensureSessionFdInfoExists (session);
415  return &session->fdInfo->fileset;
416}
417
418void
419tr_fdFileClose (tr_session * s, const tr_torrent * tor, tr_file_index_t i)
420{
421  struct tr_cached_file * o;
422
423  if ((o = fileset_lookup (get_fileset (s), tr_torrentId (tor), i)))
424    {
425      /* flush writable files so that their mtimes will be
426       * up-to-date when this function returns to the caller... */
427      if (o->is_writable)
428        tr_sys_file_flush (o->fd, NULL);
429
430      cached_file_close (o);
431    }
432}
433
434tr_sys_file_t
435tr_fdFileGetCached (tr_session * s, int torrent_id, tr_file_index_t i, bool writable)
436{
437  struct tr_cached_file * o = fileset_lookup (get_fileset (s), torrent_id, i);
438
439  if (!o || (writable && !o->is_writable))
440    return TR_BAD_SYS_FILE;
441
442  o->used_at = tr_time ();
443  return o->fd;
444}
445
446bool
447tr_fdFileGetCachedMTime (tr_session * s, int torrent_id, tr_file_index_t i, time_t * mtime)
448{
449  bool success;
450  tr_sys_path_info info;
451  struct tr_cached_file * o = fileset_lookup (get_fileset (s), torrent_id, i);
452
453  if ((success = (o != NULL) && tr_sys_file_get_info (o->fd, &info, NULL)))
454    *mtime = info.last_modified_at;
455
456  return success;
457}
458
459void
460tr_fdTorrentClose (tr_session * session, int torrent_id)
461{
462  assert (tr_sessionIsLocked (session));
463
464  fileset_close_torrent (get_fileset (session), torrent_id);
465}
466
467/* returns an fd on success, or a TR_BAD_SYS_FILE on failure and sets errno */
468tr_sys_file_t
469tr_fdFileCheckout (tr_session             * session,
470                   int                      torrent_id,
471                   tr_file_index_t          i,
472                   const char             * filename,
473                   bool                     writable,
474                   tr_preallocation_mode    allocation,
475                   uint64_t                 file_size)
476{
477  struct tr_fileset * set = get_fileset (session);
478  struct tr_cached_file * o = fileset_lookup (set, torrent_id, i);
479
480  if (o && writable && !o->is_writable)
481    cached_file_close (o); /* close it so we can reopen in rw mode */
482  else if (!o)
483    o = fileset_get_empty_slot (set);
484
485  if (!cached_file_is_open (o))
486    {
487      const int err = cached_file_open (o, filename, writable, allocation, file_size);
488      if (err)
489        {
490          errno = err;
491          return TR_BAD_SYS_FILE;
492        }
493
494      dbgmsg ("opened '%s' writable %c", filename, writable?'y':'n');
495      o->is_writable = writable;
496    }
497
498  dbgmsg ("checking out '%s'", filename);
499  o->torrent_id = torrent_id;
500  o->file_index = i;
501  o->used_at = tr_time ();
502  return o->fd;
503}
504
505/***
506****
507****  Sockets
508****
509***/
510
511tr_socket_t
512tr_fdSocketCreate (tr_session * session,
513                   int          domain,
514                   int          type)
515{
516  tr_socket_t s = TR_BAD_SOCKET;
517  struct tr_fdInfo * gFd;
518  assert (tr_isSession (session));
519
520  ensureSessionFdInfoExists (session);
521  gFd = session->fdInfo;
522
523  if (gFd->peerCount < session->peerLimit)
524    if ((s = socket (domain, type, 0)) == TR_BAD_SOCKET)
525      if (sockerrno != EAFNOSUPPORT)
526        {
527          char err_buf[512];
528          tr_logAddError (_("Couldn't create socket: %s"),
529                          tr_net_strerror (err_buf, sizeof (err_buf), sockerrno));
530        }
531
532  if (s != TR_BAD_SOCKET)
533    ++gFd->peerCount;
534
535  assert (gFd->peerCount >= 0);
536
537  if (s != TR_BAD_SOCKET)
538    {
539      static bool buf_logged = false;
540      if (!buf_logged)
541        {
542          int i;
543          socklen_t size = sizeof (int);
544          buf_logged = true;
545          getsockopt (s, SOL_SOCKET, SO_SNDBUF, (void *) &i, &size);
546          tr_logAddDebug ("SO_SNDBUF size is %d", i);
547          getsockopt (s, SOL_SOCKET, SO_RCVBUF, (void *) &i, &size);
548          tr_logAddDebug ("SO_RCVBUF size is %d", i);
549        }
550    }
551
552  return s;
553}
554
555tr_socket_t
556tr_fdSocketAccept (tr_session  * s,
557                   tr_socket_t   sockfd,
558                   tr_address  * addr,
559                   tr_port     * port)
560{
561  tr_socket_t fd;
562  socklen_t len;
563  struct tr_fdInfo * gFd;
564  struct sockaddr_storage sock;
565
566  assert (tr_isSession (s));
567  assert (addr);
568  assert (port);
569
570  ensureSessionFdInfoExists (s);
571  gFd = s->fdInfo;
572
573  len = sizeof (struct sockaddr_storage);
574  fd = accept (sockfd, (struct sockaddr *) &sock, &len);
575
576  if (fd != TR_BAD_SOCKET)
577    {
578      if ((gFd->peerCount < s->peerLimit)
579          && tr_address_from_sockaddr_storage (addr, port, &sock))
580        {
581          ++gFd->peerCount;
582        }
583      else
584        {
585          tr_netCloseSocket (fd);
586          fd = TR_BAD_SOCKET;
587        }
588    }
589
590  return fd;
591}
592
593void
594tr_fdSocketClose (tr_session  * session,
595                  tr_socket_t   fd)
596{
597  assert (tr_isSession (session));
598
599  if (session->fdInfo != NULL)
600    {
601      struct tr_fdInfo * gFd = session->fdInfo;
602
603      if (fd != TR_BAD_SOCKET)
604        {
605          tr_netCloseSocket (fd);
606          --gFd->peerCount;
607        }
608
609      assert (gFd->peerCount >= 0);
610    }
611}
Note: See TracBrowser for help on using the repository browser.