source: trunk/daemon/watch.c @ 10504

Last change on this file since 10504 was 10504, checked in by charles, 12 years ago

(trunk daemon) #3158 "Possible crash when using inotify" -- fixed in trunk for 2.00

  • Property svn:keywords set to Date Rev Author Id
File size: 6.8 KB
Line 
1/*
2 * This file Copyright (C) 2009-2010 Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: watch.c 10504 2010-04-21 01:31:23Z charles $
11 */
12#ifdef WITH_INOTIFY
13  #include <sys/inotify.h>
14  #include <sys/select.h>
15  #include <unistd.h> /* close */
16#else
17  #include <sys/types.h> /* stat */
18  #include <sys/stat.h> /* stat */
19  #include <event.h> /* evbuffer */
20#endif
21
22#include <errno.h>
23#include <string.h> /* strstr */
24
25#include <dirent.h> /* readdir */
26
27#include <libtransmission/transmission.h>
28#include <libtransmission/utils.h> /* tr_buildPath(), tr_inf() */
29#include "watch.h"
30
31struct dtr_watchdir
32{
33    tr_session * session;
34    char * dir;
35    dtr_watchdir_callback * callback;
36#ifdef WITH_INOTIFY
37    int inotify_fd;
38#else /* readdir implementation */
39    time_t lastTimeChecked;
40    struct evbuffer * lastFiles;
41#endif
42};
43
44static tr_bool
45str_has_suffix( const char *str, const char *suffix )
46{
47    const size_t str_len = strlen( str );
48    const size_t suffix_len = strlen( suffix );
49
50    if( str_len < suffix_len )
51        return FALSE;
52
53    return !strncasecmp( str + str_len - suffix_len, suffix, suffix_len );
54}
55
56/***
57****  INOTIFY IMPLEMENTATION
58***/
59
60#if defined(WITH_INOTIFY)
61
62/* how many inotify events to try to batch into a single read */
63#define EVENT_BATCH_COUNT 50
64/* size of the event structure, not counting name */
65#define EVENT_SIZE  (sizeof (struct inotify_event))
66/* reasonable guess as to size of 50 events */
67#define BUF_LEN (EVENT_BATCH_COUNT * (EVENT_SIZE + 16) + 2048)
68
69#define DTR_INOTIFY_MASK (IN_CLOSE_WRITE|IN_MOVED_TO|IN_ONLYDIR)
70
71static void
72watchdir_new_impl( dtr_watchdir * w )
73{
74    int i;
75    DIR * odir;
76    w->inotify_fd = inotify_init( );
77
78    if( w->inotify_fd < 0 )
79    {
80        i = -1;
81    }
82    else
83    {
84        tr_inf( "Using inotify to watch directory \"%s\"", w->dir );
85        i = inotify_add_watch( w->inotify_fd, w->dir, DTR_INOTIFY_MASK );
86    }
87
88    if( i < 0 )
89    {
90        tr_err( "Unable to watch \"%s\": %s", w->dir, strerror( errno ) );
91    }
92    else if(( odir = opendir( w->dir )))
93    {
94        struct dirent * d;
95
96        while(( d = readdir( odir )))
97        {
98            const char * name = d->d_name;
99
100            if( !name || *name=='.' ) /* skip dotfiles */
101                continue;
102            if( !str_has_suffix( name, ".torrent" ) ) /* skip non-torrents */
103                continue;
104
105            tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir );
106            w->callback( w->session, w->dir, name );
107        }
108
109        closedir( odir );
110    }
111
112}
113static void
114watchdir_free_impl( dtr_watchdir * w )
115{
116    if( w->inotify_fd >= 0 )
117    {
118        inotify_rm_watch( w->inotify_fd, DTR_INOTIFY_MASK );
119
120        close( w->inotify_fd );
121    }
122}
123static void
124watchdir_update_impl( dtr_watchdir * w )
125{
126    int ret;
127    fd_set rfds;
128    struct timeval time;
129    const int fd = w->inotify_fd;
130
131    /* timeout after one second */
132    time.tv_sec = 1;
133    time.tv_usec = 0;
134
135    /* make the fd_set hold the inotify fd */
136    FD_ZERO( &rfds );
137    FD_SET( fd, &rfds );
138
139    /* check for added files */
140    ret = select( fd+1, &rfds, NULL, NULL, &time );
141    if( ret < 0 ) {
142        perror( "select" );
143    } else if( !ret ) {
144        /* timed out! */
145    } else if( FD_ISSET( fd, &rfds ) ) {
146        int i = 0;
147        char buf[BUF_LEN];
148        int len = read( fd, buf, sizeof( buf ) );
149        while (i < len) {
150            struct inotify_event * event = (struct inotify_event *) &buf[i];
151            const char * name = event->name;
152            if( str_has_suffix( name, ".torrent" ) )
153            {
154                tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir );
155                w->callback( w->session, w->dir, name );
156            }
157            i += EVENT_SIZE +  event->len;
158        }
159    }
160}
161
162#else /* WITH_INOTIFY */
163
164/***
165****  READDIR IMPLEMENTATION
166***/
167
168#define WATCHDIR_POLL_INTERVAL_SECS 10
169
170#define FILE_DELIMITER '\0'
171
172static void
173watchdir_new_impl( dtr_watchdir * w UNUSED )
174{
175    tr_inf( "Using readdir to watch directory \"%s\"", w->dir );
176    w->lastFiles = evbuffer_new( );
177}
178static void
179watchdir_free_impl( dtr_watchdir * w )
180{
181    evbuffer_free( w->lastFiles );
182}
183static void
184add_file_to_list( struct evbuffer * buf, const char * filename, size_t len )
185{
186    const char delimiter = FILE_DELIMITER;
187    evbuffer_add( buf, &delimiter, 1 );
188    evbuffer_add( buf, filename, len );
189    evbuffer_add( buf, &delimiter, 1 );
190}
191static tr_bool
192is_file_in_list( struct evbuffer * buf, const char * filename, size_t len )
193{
194    tr_bool in_list;
195    struct evbuffer * test = evbuffer_new( );
196    add_file_to_list( test, filename, len );
197    in_list = evbuffer_find( buf, EVBUFFER_DATA( test ), EVBUFFER_LENGTH( test ) ) != NULL;
198    evbuffer_free( test );
199    return in_list;
200}
201static void
202watchdir_update_impl( dtr_watchdir * w )
203{
204    struct stat sb;
205    DIR * odir;
206    const time_t oldTime = w->lastTimeChecked;
207    const char * dirname = w->dir;
208    struct evbuffer * curFiles = evbuffer_new( );
209
210    if ( ( oldTime + WATCHDIR_POLL_INTERVAL_SECS < time( NULL ) )
211         && !stat( dirname, &sb )
212         && S_ISDIR( sb.st_mode )
213         && (( odir = opendir( dirname ))) )
214    {
215        struct dirent * d;
216
217        for( d = readdir( odir ); d != NULL; d = readdir( odir ) )
218        {
219            size_t len;
220            const char * name = d->d_name;
221
222            if( !name || *name=='.' ) /* skip dotfiles */
223                continue;
224            if( !str_has_suffix( name, ".torrent" ) ) /* skip non-torrents */
225                continue;
226
227            len = strlen( name );
228            add_file_to_list( curFiles, name, len );
229
230            /* if this file wasn't here last time, try adding it */
231            if( !is_file_in_list( w->lastFiles, name, len ) ) {
232                tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir );
233                w->callback( w->session, w->dir, name );
234            }
235        }
236
237        closedir( odir );
238        w->lastTimeChecked = time( NULL );
239        evbuffer_free( w->lastFiles );
240        w->lastFiles = curFiles;
241    }
242}
243
244#endif
245
246/***
247****
248***/
249
250dtr_watchdir*
251dtr_watchdir_new( tr_session * session, const char * dir, dtr_watchdir_callback * callback )
252{
253    dtr_watchdir * w = tr_new0( dtr_watchdir, 1 );
254
255    w->session = session;
256    w->dir = tr_strdup( dir );
257    w->callback = callback;
258
259    watchdir_new_impl( w );
260
261    return w;
262}
263
264void
265dtr_watchdir_update( dtr_watchdir * w )
266{
267    if( w != NULL )
268        watchdir_update_impl( w );
269}
270
271void
272dtr_watchdir_free( dtr_watchdir * w )
273{
274    if( w != NULL )
275    {
276        watchdir_free_impl( w );
277        tr_free( w->dir );
278        tr_free( w );
279    }
280}
Note: See TracBrowser for help on using the repository browser.