[go: up one dir, main page]

File: tilda-lock-files.c

package info (click to toggle)
tilda 2.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,708 kB
  • sloc: ansic: 8,286; sh: 4,917; makefile: 169; xml: 54; sed: 16
file content (369 lines) | stat: -rw-r--r-- 11,125 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
/*
 * This is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "tilda-lock-files.h"

#include "debug.h"

#include <fcntl.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <stdio.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <unistd.h>

static gchar *create_lock_file (struct lock_info *lock);
static struct lock_info *islockfile (const gchar *filename);
static GSList *getPids();
static gint remove_stale_lock_files (void);
static gint get_instance_number (void);

gboolean
tilda_lock_files_obtain_instance_lock (struct lock_info * lock_info)
{
    /* Remove stale lock files */
    remove_stale_lock_files ();

    /* The global lock file is used to synchronize the start-up of multiple simultaneously starting tilda processes.
     * The processes will synchronize on a lock file named lock_0_0, such that the part of determining the instance
     * number and creating the per process lock file (lock_<pid>_<instance>) is atomic. Without this it could
     * happen, that a second tilda instance was trying to determine its instance number before the first instance
     * had finished creating its lock file, this resulted in two processes with the same instance number and could lead
     * to corruption of the config file.
     * In order to test if this works the following shell command can be used: "tilda & tilda && fg", which causes
     * two tilda processes to be started simultaneously. See:
     *
     *     http://stackoverflow.com/questions/3004811/how-do-you-run-multiple-programs-from-a-bash-script
     */
    struct lock_info global_lock;
    global_lock.instance = 0;
    global_lock.pid = 0;
    gchar *global_lock_file = NULL;

    global_lock_file = create_lock_file(&global_lock);

    if(global_lock_file == NULL) {
        perror("Error creating global lock file.");
        return FALSE;
    }

    int lockResult = flock(global_lock.file_descriptor, LOCK_EX);

    if (lockResult == -1) {
        perror("Could not acquire global tilda lock file lock.");
        return FALSE;
    }

    /* Start of atomic section. */
    lock_info->pid = getpid ();
    lock_info->instance = get_instance_number ();
    lock_info->lock_file = create_lock_file (lock_info);
    /* End of atomic section */

    flock(global_lock.file_descriptor, LOCK_UN);
    close(lock_info->file_descriptor);

    g_free(global_lock_file);

    return TRUE;
}

void tilda_lock_files_free (struct lock_info * lock_info)
{
    remove (lock_info->lock_file);
    g_free (lock_info->lock_file);
    lock_info->lock_file = NULL;
}

/**
* If lock->pid is 0 then the file is not opened exclusively. Instead flock() must be used to obtain a lock.
* Otherwise an exclusive lock file is created for the process.
*/
static gchar *create_lock_file (struct lock_info *lock)
{
    DEBUG_FUNCTION ("create_lock_file");
    DEBUG_ASSERT (lock != NULL);
    DEBUG_ASSERT (lock->instance >= 0);
    DEBUG_ASSERT (lock->pid >= 0);

    gint ret;
    gchar *lock_file_full;
    gchar *lock_dir = g_build_filename (g_get_user_cache_dir (), "tilda", "locks", NULL);
    gchar *lock_file = g_strdup_printf ("lock_%d_%d", lock->pid, lock->instance);

    /* Make the ~/.cache/tilda/locks directory */
    ret = g_mkdir_with_parents (lock_dir,  S_IRUSR | S_IWUSR | S_IXUSR);

    if (ret == -1)
        goto mkdir_fail;

    /* Create the full path to the lock file */
    lock_file_full = g_build_filename (lock_dir, lock_file, NULL);

    /* Create the lock file */
    if(lock->pid == 0) {
        ret = open(lock_file_full, O_CREAT, S_IRUSR | S_IWUSR);
    } else {
        ret = open(lock_file_full, O_WRONLY | O_CREAT | O_EXCL, 0);
    }

    if (ret == -1)
        goto creat_fail;

    lock->file_descriptor = ret;

    DEBUG_FUNCTION_MESSAGE("create_lock_file", "created lock file at '%s'.", lock_file_full);

    g_free (lock_file);
    g_free (lock_dir);

    return lock_file_full;

    /* Free memory and return NULL */
    creat_fail:
    g_free (lock_file_full);
    mkdir_fail:
    g_free (lock_file);
    g_free (lock_dir);

    return NULL;
}

/**
 * Check if a filename corresponds to a valid lockfile created by tilda. Note,
 * that this function does NOT check whether it is a stale lock file. This
 * function will return a lock_info that corresponds to the lock file, if the
 * file uses a valid tilda lock file name. The caller needs to check if the
 * lock_info points to a stale lock file or if its a currently valid lock file
 * that belongs to an active tilda process.
 *
 * @param filename the filename to check, should point to a file with the format
 * <code>lock_&lt;pid&gt;_&lt;instance&gt;</code> in the tilda lock directory
 * (i.e., <code>${XDG_CACHE_DIR}/tilda/locks</code>).
 * @return a new struct lock_info, which must be freed by the caller.
 *
 * Success: struct lock_info will be returned, with the pid and instance members
 * set to match the the fields encoded in filename. The file_descriptor and
 * lock_file members will not be set.
 * Failure: returns NULL
 */
static struct lock_info *islockfile (const gchar *filename)
{
    DEBUG_FUNCTION ("islockfile");
    DEBUG_ASSERT (filename != NULL);

    struct lock_info *lock;

    lock = g_malloc0 (sizeof (struct lock_info));

    if (lock == NULL)
        return NULL;

    gboolean matches = g_str_has_prefix (filename, "lock_");
    gboolean islock = FALSE;
    gchar *pid_s, *instance_s;

    if (matches) /* we are prefixed with "lock_" and are probably a lock */
    {
        pid_s = strstr (filename, "_");

        if (pid_s) /* we have a valid pid */
        {
            /* Advance the pointer past the underscore */
            pid_s++;

            lock->pid = atoi (pid_s);
            instance_s = strstr (pid_s, "_");

            if (lock->pid > 0 && instance_s)
            {
                /* Advance the pointer past the underscore */
                instance_s++;

                /* Extract the instance number and store it */
                lock->instance = atoi (instance_s);

                /* we parsed everything, so yes, we were a lock */
                islock = TRUE;
            }
        }
    }

    if (!islock)
    {
        g_free (lock);
        lock = NULL;
    }

    return lock;
}

/**
* Gets a list of the pids in
*/
static GSList *getPids() {
    GSList *pids = NULL;
    FILE *ps_output;
    const gchar ps_command[] = "ps -C tilda -o pid=";
    gchar buf[16]; /* Really shouldn't need more than 6 */

    if ((ps_output = popen (ps_command, "r")) == NULL) {
        g_printerr (_("Unable to run command: `%s'\n"), ps_command);
        return NULL;
    }

    /* The popen() succeeded, get all of the pids */
    while (fgets (buf, sizeof(buf), ps_output) != NULL)
        pids = g_slist_append (pids, GINT_TO_POINTER (atoi (buf)));

    /* We've read all of the pids, exit */
    pclose (ps_output);
    return pids;
}

/**
 * Remove stale lock files from the ~/.tilda/locks/ directory.
 *
 * Success: returns 0
 * Failure: returns non-zero
 */
static gint remove_stale_lock_files (void)
{
    DEBUG_FUNCTION ("remove_stale_lock_files");

    GSList *pids = getPids();
    if(pids == NULL) {
        return -1;
    }

    struct lock_info *lock;
    gchar *lock_dir = g_build_filename (g_get_user_cache_dir (), "tilda", "locks", NULL);
    gchar *remove_file;
    gchar *filename;
    GDir *dir;

    /* if the lock dir does not exist then there are no stale lock files to remove. */
    if (!g_file_test (lock_dir, G_FILE_TEST_EXISTS)) {
        g_free (lock_dir);
        return 0;
    }

    /* Open the lock directory for reading */
    dir = g_dir_open (lock_dir, 0, NULL);

    if (dir == NULL)
    {
        g_printerr (_("Unable to open lock directory: %s\n"), lock_dir);
        g_free (lock_dir);
        return -2;
    }

    /* For each possible lock file, check if it is a lock, and see if
     * it matches one of the running tildas */
    while ((filename = (gchar*) g_dir_read_name (dir)) != NULL)
    {
        lock = islockfile (filename);

        if (lock && (g_slist_find (pids, GINT_TO_POINTER (lock->pid)) == NULL))
        {
            /* We have found a stale element */
            remove_file = g_build_filename (lock_dir, filename, NULL);
            remove (remove_file);
            g_free (remove_file);
        }

        if (lock)
            g_free (lock);
    }

    g_dir_close (dir);
    g_free (lock_dir);
    g_slist_free(pids);

    return 0;
}

static gint _cmp_locks(gconstpointer a, gconstpointer b, gpointer userdata) {
    return GPOINTER_TO_INT (a) - GPOINTER_TO_INT (b);
}

/**
 * get_instance_number ()
 *
 * Gets the next available tilda instance number. This will always pick the
 * lowest non-running tilda available.
 *
 * Success: return next available instance number (>=0)
 * Failure: return 0
 */
static gint get_instance_number ()
{
    DEBUG_FUNCTION ("get_instance_number");

    gchar *name;

    GSequence *seq;
    GSequenceIter *iter;
    gint lowest_lock_instance = 0;
    gint current_lock_instance;

    GDir *dir;
    struct lock_info *lock;
    gchar *lock_dir = g_build_filename (g_get_user_cache_dir (), "tilda", "locks", NULL);

    /* Open the lock directory */
    dir = g_dir_open (lock_dir, 0, NULL);

    /* Check for failure to open */
    if (dir == NULL)
    {
        g_printerr (_("Unable to open lock directory: %s\n"), lock_dir);
        g_free (lock_dir);
        return 0;
    }

    /* Look through every file in the lock directory, and see if it is a lock file.
     * If it is a lock file, insert it in a sorted sequence. */
    seq = g_sequence_new(NULL);
    while ((name = (gchar*)g_dir_read_name (dir)) != NULL)
    {
        lock = islockfile (name);

        if (lock != NULL)
        {
            g_sequence_insert_sorted(seq, GINT_TO_POINTER(lock->instance), _cmp_locks, NULL);
            g_free (lock);
        }
    }

    g_dir_close (dir);
    g_free (lock_dir);

    /* We iterate the sorted sequence of lock instances to find the first (lowest) number *not* taken. */
    for (iter = g_sequence_get_begin_iter(seq); !g_sequence_iter_is_end(iter); iter = g_sequence_iter_next(iter)) {
        current_lock_instance = GPOINTER_TO_INT(g_sequence_get(iter));
        if (lowest_lock_instance < current_lock_instance)
            break;
        else
            lowest_lock_instance = current_lock_instance + 1;
    }

    g_sequence_free(seq);

    DEBUG_FUNCTION_MESSAGE("get_instance_number", "assigned instance: %d", lowest_lock_instance);

    return lowest_lock_instance;
}