~ubuntu-branches/ubuntu/precise/nvidia-settings/precise-proposed

« back to all changes in this revision

Viewing changes to src/app-profiles.c

  • Committer: Package Import Robot
  • Author(s): Alberto Milone
  • Date: 2013-12-11 15:23:40 UTC
  • mfrom: (1.3.4)
  • Revision ID: package-import@ubuntu.com-20131211152340-6j6x4ldvu4ll1bu1
Tags: 331.20-0ubuntu0.0.1
* debian/patches/series:
  - Do not apply 01_allow_dark_themes.dpatch.
* debian/patches/07_remove_features_for_legacy.patch:
  - Do not expose features that are not available in the legacy
    drivers.
* debian/patches/08_add_prime_support.patch:
  - Add support for PRIME switching. An additional
    tab provides support for switching between GPUs.
    This is only visible if nvidia-prime (>= 0.5) is
    installed and reports that the system supports
    hybrid graphics. No hard dependency on nvidia-prime
    is therefore required (LP: #1259237).
* debian/patches/09_do_not_complain_if_nvidia_is_missing.patch:
  - Disable the warning dialog since it suggests to run
    nvidia-xconfig, which we don't need. This would also break
    PRIME.
* debian/control.in, debian/postinst.in, debian/postrm.in,
  debian/prerm.in, debian/rules:
  - Drop alternatives and remove templates, as we only have
    one nvidia-settings for all the driver flavours.
* debian/dirs, debian/install,
  debian/nvidia-settings-autostart.desktop,
  debian/nvidia-settings.desktop:
  - Install the icon and the desktop files.
* debian/control:
  - Add ${misc:Depends}.
  - Build depend on libvdpau-dev and depend on libvdpau1.
  - Create transitional packages for 319, 319-updates, 313-updates,
    310, 310-updates, 304, 304-updates, experimental-304, updates.
  - Remove lpia.
  - Depend on screen-resolution-extra (>= 0.14ubuntu2.1).
* debian/rules:
  - Pass the destdir argument in uppercase to match the variable.
  - Add download-sources target.
  - Clean action in rules to target "clean" instead of "distclean".
  - Do not compress .c and .mk files.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * nvidia-settings: A tool for configuring the NVIDIA X driver on Unix
 
3
 * and Linux systems.
 
4
 *
 
5
 * Copyright (C) 2013 NVIDIA Corporation.
 
6
 *
 
7
 * This program is free software; you can redistribute it and/or modify it
 
8
 * under the terms and conditions of the GNU General Public License,
 
9
 * version 2, as published by the Free Software Foundation.
 
10
 *
 
11
 * This program is distributed in the hope that it will be useful, but WITHOUT
 
12
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 
13
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 
14
 * more details.
 
15
 *
 
16
 * You should have received a copy of the GNU General Public License
 
17
 * along with this program.  If not, see <http://www.gnu.org/licenses>.
 
18
 */
 
19
 
 
20
/*
 
21
 * app-profiles.c - this source file contains functions for querying and
 
22
 * assigning application profile settings, as well as parsing and saving
 
23
 * application profile configuration files.
 
24
 */
 
25
 
 
26
#define _GNU_SOURCE
 
27
 
 
28
#include <stdlib.h>
 
29
#include <stdio.h>
 
30
#include <string.h>
 
31
#include <assert.h>
 
32
#include <sys/types.h>
 
33
#include <sys/stat.h>
 
34
#include <unistd.h>
 
35
#include <errno.h>
 
36
#include <dirent.h>
 
37
#include <ctype.h>
 
38
#include <time.h>
 
39
#include "common-utils.h"
 
40
#include "app-profiles.h"
 
41
#include "msg.h"
 
42
 
 
43
static char *slurp(FILE *fp)
 
44
{
 
45
    int eof = FALSE;
 
46
    char *text = strdup("");
 
47
    char *new_text;
 
48
    char *line = NULL;
 
49
 
 
50
    while (text && !eof) {
 
51
        line = fget_next_line(fp, &eof);
 
52
        if (!eof) {
 
53
            new_text = nvstrcat(text, "\n", line, NULL);
 
54
            free(text);
 
55
            text = new_text;
 
56
        }
 
57
    }
 
58
 
 
59
    free(line);
 
60
 
 
61
    return text;
 
62
}
 
63
 
 
64
static void splice_string(char **s, size_t b, size_t e, const char *replace)
 
65
{
 
66
    char *tail = strdup(*s + e);
 
67
    *s = realloc(*s, b + strlen(replace) + strlen(tail) + 1);
 
68
    if (!*s) {
 
69
        return;
 
70
    }
 
71
    sprintf(*s + b, "%s%s", replace, tail);
 
72
    free(tail);
 
73
}
 
74
 
 
75
#define HEX_DIGITS "0123456789abcdefABCDEF"
 
76
 
 
77
char *nv_app_profile_cfg_file_syntax_to_json(const char *orig_s)
 
78
{
 
79
    char *s = strdup(orig_s);
 
80
 
 
81
    int quoted = FALSE;
 
82
    char *tok;
 
83
    size_t start, end, size;
 
84
    unsigned long long val;
 
85
    char *old_substr = NULL;
 
86
    char *endptr;
 
87
    char *new_substr = NULL;
 
88
 
 
89
    tok = s;
 
90
    while ((tok = strpbrk(tok, "\\\"#" HEX_DIGITS))) {
 
91
        switch (*tok) {
 
92
        case '\"':
 
93
            // Quotation mark
 
94
            quoted = !quoted;
 
95
            tok++;
 
96
            break;
 
97
        case '\\':
 
98
            // Escaped character
 
99
            tok++;
 
100
            if (*tok) {
 
101
                tok++;
 
102
            }
 
103
            break;
 
104
        case '#':
 
105
            // Comment
 
106
            if (!quoted) {
 
107
                char *end_tok = nvstrchrnul(tok, '\n');
 
108
                start = tok - s;
 
109
                end = end_tok - s;
 
110
                splice_string(&s, start, end, "");
 
111
                if (!s) {
 
112
                    goto fail;
 
113
                }
 
114
                tok = s + start;
 
115
            } else {
 
116
                tok++;
 
117
            }
 
118
            break;
 
119
        case '0': case '1': case '2': case '3': case '4':
 
120
        case '5': case '6': case '7': case '8': case '9':
 
121
        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
 
122
        case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
 
123
            // Numeric value
 
124
            size = strspn(tok, "Xx." HEX_DIGITS);
 
125
            if ((tok[0] == '0') &&
 
126
                (tok[1] == 'x' || tok[1] == 'X' || isdigit(tok[1])) &&
 
127
                !quoted) {
 
128
                old_substr = nvstrndup(tok, size);
 
129
                if (!old_substr) {
 
130
                    goto fail;
 
131
                }
 
132
                errno = 0;
 
133
                val = strtoull(old_substr, &endptr, 0);
 
134
                if (errno || (endptr - old_substr != strlen(old_substr))) {
 
135
                    // Invalid conversion, skip this string
 
136
                    tok += size;
 
137
                    free(old_substr); old_substr = NULL;
 
138
                } else {
 
139
                    new_substr = nvasprintf("%llu", val);
 
140
                    if (!new_substr) {
 
141
                        goto fail;
 
142
                    } else {
 
143
                        start = tok - s;
 
144
                        end = tok - s + size;
 
145
                        splice_string(&s, start, end, new_substr);
 
146
                        free(new_substr); new_substr = NULL;
 
147
                        free(old_substr); old_substr = NULL;
 
148
                        tok = s + start;
 
149
                    }
 
150
                }
 
151
            } else {
 
152
                // Not hex or octal; let the JSON parser deal with it
 
153
                tok += size;
 
154
            }
 
155
            break;
 
156
        default:
 
157
            assert(!"Unhandled character");
 
158
            break;
 
159
        }
 
160
    }
 
161
 
 
162
    assert(!new_substr);
 
163
    assert(!old_substr);
 
164
    return s;
 
165
 
 
166
fail:
 
167
    free(old_substr);
 
168
    free(new_substr);
 
169
    free(s);
 
170
    return NULL;
 
171
}
 
172
 
 
173
static int open_and_stat(const char *filename, const char *perms, FILE **fp, struct stat *stat_buf)
 
174
{
 
175
    int ret;
 
176
    *fp = fopen(filename, perms);
 
177
    if (!*fp) {
 
178
        if (errno != ENOENT) {
 
179
            nv_error_msg("Could not open file %s (%s)", filename, strerror(errno));
 
180
        }
 
181
        return -1;
 
182
    }
 
183
 
 
184
    ret = fstat(fileno(*fp), stat_buf);
 
185
    if (ret == -1) {
 
186
        nv_error_msg("Could not stat file %s (%s)", filename, strerror(errno));
 
187
        fclose(*fp);
 
188
    }
 
189
    return ret;
 
190
}
 
191
 
 
192
static char *nv_dirname(const char *path)
 
193
{
 
194
    char *last_slash = strrchr(path, '/');
 
195
    if (last_slash) {
 
196
        return nvstrndup(path, last_slash - path);
 
197
    } else {
 
198
        return nvstrdup(".");
 
199
    }
 
200
}
 
201
 
 
202
static json_t *app_profile_config_insert_file_object(AppProfileConfig *config, json_t *new_file)
 
203
{
 
204
    json_t *json_filename, *json_new_filename;
 
205
    char *dirname;
 
206
    const char *filename, *new_filename;
 
207
    json_t *file;
 
208
    json_t *order, *file_order;
 
209
    size_t new_file_major, new_file_minor, file_order_major, file_order_minor;
 
210
    size_t i;
 
211
    size_t num_files;
 
212
 
 
213
    json_new_filename = json_object_get(new_file, "filename");
 
214
 
 
215
    assert(json_new_filename);
 
216
    new_filename = json_string_value(json_new_filename);
 
217
 
 
218
    assert(nv_app_profile_config_check_valid_source_file(config,
 
219
                                                         new_filename,
 
220
                                                         NULL));
 
221
 
 
222
    // Determine the correct location of the file in the search path
 
223
    dirname = NULL;
 
224
 
 
225
    new_file_major = -1;
 
226
    for (i = 0; i < config->search_path_count; i++) {
 
227
        if (!strcmp(new_filename, config->search_path[i])) {
 
228
            new_file_major = i;
 
229
            break;
 
230
        } else {
 
231
            if (!dirname) {
 
232
                dirname = nv_dirname(new_filename);
 
233
            }
 
234
            if (!strcmp(dirname, config->search_path[i])) {
 
235
                new_file_major = i;
 
236
                break;
 
237
            }
 
238
        }
 
239
    }
 
240
    free(dirname);
 
241
 
 
242
    new_file_minor = 0;
 
243
    num_files = json_array_size(config->parsed_files);
 
244
 
 
245
    for (i = 0; i < num_files; i++) {
 
246
        file = json_array_get(config->parsed_files, i);
 
247
        file_order = json_object_get(file, "order");
 
248
        file_order_major = json_integer_value(json_object_get(file_order, "major"));
 
249
        file_order_minor = json_integer_value(json_object_get(file_order, "minor"));
 
250
        json_filename = json_object_get(file, "filename");
 
251
        assert(json_filename);
 
252
        filename = json_string_value(json_filename);
 
253
        if (file_order_major < new_file_major) {
 
254
        } else if (file_order_major == new_file_major) {
 
255
            if (strcoll(filename, new_filename) > 0) {
 
256
                break;
 
257
            }
 
258
            new_file_minor++;
 
259
        } else {
 
260
            break;
 
261
        }
 
262
    }
 
263
 
 
264
    // Mark the order of the file
 
265
    order = json_object_get(new_file, "order");
 
266
    json_object_set_new(order, "major", json_integer(new_file_major));
 
267
    json_object_set_new(order, "minor", json_integer(new_file_minor));
 
268
 
 
269
    // Add the new file
 
270
    json_array_insert(config->parsed_files, i, new_file);
 
271
 
 
272
    // Bump up minor for files after this one with the same major
 
273
    num_files = json_array_size(config->parsed_files);
 
274
 
 
275
    for ( ; i < num_files; i++) {
 
276
        file = json_array_get(config->parsed_files, i);
 
277
        file_order = json_object_get(file, "order");
 
278
        file_order_major = json_integer_value(json_object_get(order, "major"));
 
279
        file_order_minor = json_integer_value(json_object_get(order, "minor"));
 
280
        if (file_order_major > new_file_major) {
 
281
            break;
 
282
        }
 
283
        json_object_set_new(file_order, "minor", json_integer(file_order_minor+1));
 
284
    }
 
285
 
 
286
    return new_file;
 
287
}
 
288
 
 
289
 
 
290
/*
 
291
 * Create a new empty file object and adds it to the configuration.
 
292
 */
 
293
static json_t *app_profile_config_new_file(AppProfileConfig *config,
 
294
                                           const char *filename)
 
295
{
 
296
    json_t *new_file = json_object();
 
297
 
 
298
    json_object_set_new(new_file, "filename", json_string(filename));
 
299
    json_object_set_new(new_file, "rules", json_array());
 
300
    json_object_set_new(new_file, "profiles", json_object());
 
301
    json_object_set_new(new_file, "dirty", json_false());
 
302
    json_object_set_new(new_file, "new", json_true());
 
303
    // order is set by app_profile_config_insert_file_object() below
 
304
 
 
305
    new_file = app_profile_config_insert_file_object(config, new_file);
 
306
 
 
307
    return new_file;
 
308
}
 
309
 
 
310
static char *rule_id_to_key_string(int id)
 
311
{
 
312
    char *key;
 
313
    key = nvasprintf("%d", id);
 
314
    return key;
 
315
}
 
316
 
 
317
/*
 
318
 * Constructs a profile name that is guaranteed to be unique to this
 
319
 * configuration. This is used to handle the case where there are multiple
 
320
 * profiles with the same name (an invalid configuration).
 
321
 */
 
322
static char *app_profile_config_unique_profile_name(AppProfileConfig *config,
 
323
                                                    const char *orig_name,
 
324
                                                    const char *filename,
 
325
                                                    int do_warn,
 
326
                                                    int *needs_dirty)
 
327
{
 
328
    json_t *json_gold_filename = json_object_get(config->profile_locations, orig_name);
 
329
 
 
330
    if (json_gold_filename) {
 
331
        int i = 0;
 
332
        char *new_name = NULL;
 
333
        do {
 
334
            free(new_name);
 
335
            new_name = nvasprintf("%s_duplicate_%d", orig_name, i++);
 
336
        } while (new_name && json_object_get(config->profile_locations, new_name));
 
337
        if (do_warn) {
 
338
            nv_error_msg("The profile \"%s\" in the file \"%s\" has the same name "
 
339
                         "as a profile defined in the file \"%s\", and will be renamed to \"%s\".",
 
340
                         orig_name, filename, json_string_value(json_gold_filename), new_name);
 
341
        }
 
342
        if (needs_dirty) {
 
343
            *needs_dirty = TRUE;
 
344
        }
 
345
        return new_name;
 
346
    } else {
 
347
        return strdup(orig_name);
 
348
    }
 
349
}
 
350
 
 
351
 
 
352
char *nv_app_profile_config_get_unused_profile_name(AppProfileConfig *config)
 
353
{
 
354
    char *temp_name, *unique_name;
 
355
    int salt = rand();
 
356
 
 
357
    temp_name = nvasprintf("profile_%x", salt);
 
358
    unique_name = app_profile_config_unique_profile_name(config,
 
359
                                                         temp_name,
 
360
                                                         NULL,
 
361
                                                         FALSE,
 
362
                                                         NULL);
 
363
 
 
364
    free(temp_name);
 
365
    return unique_name;
 
366
}
 
367
 
 
368
static json_t *json_settings_parse(json_t *old_settings, const char *filename)
 
369
{
 
370
    int uses_setting_objects;
 
371
    size_t i, size;
 
372
    json_t *old_setting;
 
373
    json_t *new_settings, *new_setting;
 
374
    json_t *json_key, *json_value;
 
375
 
 
376
    if (!json_is_array(old_settings)) {
 
377
        return NULL;
 
378
    }
 
379
 
 
380
    new_settings = json_array();
 
381
 
 
382
    uses_setting_objects = json_array_size(old_settings) &&
 
383
                           json_is_object(json_array_get(old_settings, 0));
 
384
 
 
385
    for (i = 0, size = json_array_size(old_settings); i < size; ) {
 
386
        old_setting = json_array_get(old_settings, i++);
 
387
        if (uses_setting_objects) {
 
388
            json_key = json_object_get(old_setting, "key");
 
389
            if (!json_key) {
 
390
                json_key = json_object_get(old_setting, "k");
 
391
            }
 
392
            json_value = json_object_get(old_setting, "value");
 
393
            if (!json_value) {
 
394
                json_value = json_object_get(old_setting, "v");
 
395
            }
 
396
        } else {
 
397
            if (i >= size) {
 
398
                nv_error_msg("App profile parse error in %s: Key/value array of odd length\n", filename);
 
399
                json_decref(new_settings);
 
400
                return NULL;
 
401
            }
 
402
            json_key = old_setting;
 
403
            json_value = json_array_get(old_settings, i++);
 
404
        }
 
405
 
 
406
        if (!json_is_string(json_key)) {
 
407
            nv_error_msg("App profile parse error in %s: Invalid key detected in settings array\n", filename);
 
408
            json_decref(new_settings);
 
409
            return NULL;
 
410
        }
 
411
 
 
412
        if (!json_is_integer(json_value) &&
 
413
            !json_is_real(json_value) &&
 
414
            !json_is_true(json_value) &&
 
415
            !json_is_false(json_value) &&
 
416
            !json_is_string(json_value)) {
 
417
            nv_error_msg("App profile parse error in %s: Invalid value detected in settings array\n", filename);
 
418
            json_decref(new_settings);
 
419
            return NULL;
 
420
        }
 
421
        new_setting = json_object();
 
422
        json_object_set(new_setting, "key", json_key);
 
423
        json_object_set(new_setting, "value", json_value);
 
424
        json_array_append_new(new_settings, new_setting);
 
425
    }
 
426
 
 
427
    return new_settings;
 
428
}
 
429
 
 
430
/*
 
431
 * Load app profile settings from an already-open file. This operation is
 
432
 * atomic: either all of the settings from the file are added to the
 
433
 * configuration, or none are.
 
434
 */
 
435
static void app_profile_config_load_file(AppProfileConfig *config,
 
436
                                         const char *filename,
 
437
                                         struct stat *stat_buf,
 
438
                                         FILE *fp)
 
439
{
 
440
    char *json_text = NULL;
 
441
    char *orig_text = NULL;
 
442
    size_t i, size;
 
443
    json_error_t error;
 
444
    json_t *orig_file = NULL;
 
445
    json_t *orig_json_profiles, *orig_json_rules;
 
446
    int next_free_rule_id = config->next_free_rule_id;
 
447
    int dirty = FALSE;
 
448
    json_t *new_file = NULL;
 
449
    json_t *new_json_profiles = NULL;
 
450
    json_t *new_json_rules = NULL;
 
451
 
 
452
    if (!S_ISREG(stat_buf->st_mode)) {
 
453
        // Silently ignore all but regular files
 
454
        goto done;
 
455
    }
 
456
 
 
457
    orig_text = slurp(fp);
 
458
 
 
459
    if (!orig_text) {
 
460
        nv_error_msg("Could not read from file %s", filename);
 
461
        goto done;
 
462
    }
 
463
 
 
464
    // Convert the file contents to JSON
 
465
    json_text = nv_app_profile_cfg_file_syntax_to_json(orig_text);
 
466
 
 
467
    if (!json_text) {
 
468
        nv_error_msg("App profile parse error in %s: text is not valid app profile configuration syntax", filename);
 
469
        goto done;
 
470
    }
 
471
 
 
472
    new_file = json_object();
 
473
 
 
474
    json_object_set_new(new_file, "dirty", json_false());
 
475
    json_object_set_new(new_file, "filename", json_string(filename));
 
476
 
 
477
    new_json_profiles = json_object();
 
478
    new_json_rules = json_array();
 
479
 
 
480
    // Parse the resulting JSON
 
481
    orig_file = json_loads(json_text, 0, &error);
 
482
 
 
483
    if (!orig_file) {
 
484
        nv_error_msg("App profile parse error in %s: %s on %s, line %d\n",
 
485
                       filename, error.text, error.source, error.line);
 
486
        goto done;
 
487
    }
 
488
 
 
489
    if (!json_is_object(orig_file)) {
 
490
        nv_error_msg("App profile parse error in %s: top-level config not an object!\n", filename);
 
491
        goto done;
 
492
    }
 
493
 
 
494
    orig_json_profiles = json_object_get(orig_file, "profiles");
 
495
 
 
496
    if (orig_json_profiles) {
 
497
        /*
 
498
         * Note: we store profiles internally as members of an object, but the
 
499
         * config file syntax uses an array to store profiles.
 
500
         */
 
501
        if (!json_is_array(orig_json_profiles)) {
 
502
            nv_error_msg("App profile parse error in %s: profiles value is not an array\n", filename);
 
503
            goto done;
 
504
        }
 
505
 
 
506
        size = json_array_size(orig_json_profiles);
 
507
        for (i = 0; i < size; i++) {
 
508
            const char *new_name;
 
509
            json_t *orig_json_profile, *orig_json_name, *orig_json_settings;
 
510
            json_t *new_json_profile, *new_json_settings;
 
511
 
 
512
            new_json_profile = json_object();
 
513
 
 
514
            orig_json_profile = json_array_get(orig_json_profiles, i);
 
515
            if (!json_is_object(orig_json_profile)) {
 
516
                goto done;
 
517
            }
 
518
 
 
519
            orig_json_name = json_object_get(orig_json_profile, "name");
 
520
            if (!json_is_string(orig_json_name)) {
 
521
                goto done;
 
522
            }
 
523
 
 
524
            orig_json_settings = json_object_get(orig_json_profile, "settings");
 
525
            new_json_settings = json_settings_parse(orig_json_settings, filename);
 
526
            if (!new_json_settings) {
 
527
                goto done;
 
528
            }
 
529
 
 
530
            new_name = app_profile_config_unique_profile_name(config,
 
531
                                                              json_string_value(orig_json_name),
 
532
                                                              filename,
 
533
                                                              TRUE,
 
534
                                                              &dirty);
 
535
            json_object_set_new(new_json_profile, "settings", new_json_settings);
 
536
 
 
537
            json_object_set_new(new_json_profiles, new_name, new_json_profile);
 
538
        }
 
539
    }
 
540
 
 
541
    orig_json_rules = json_object_get(orig_file, "rules");
 
542
 
 
543
    if (orig_json_rules) {
 
544
        if (!json_is_array(orig_json_rules)) {
 
545
            nv_error_msg("App profile parse error in %s: rules value is not an array\n", filename);
 
546
            goto done;
 
547
        }
 
548
 
 
549
        size = json_array_size(orig_json_rules);
 
550
        for (i = 0; i < size; i++) {
 
551
            int new_id;
 
552
            char *profile_name;
 
553
            json_t *orig_json_rule, *orig_json_pattern, *orig_json_profile;
 
554
            json_t *new_json_rule, *new_json_pattern;
 
555
            orig_json_rule = json_array_get(orig_json_rules, i);
 
556
 
 
557
            if (!json_is_object(orig_json_rule)) {
 
558
                goto done;
 
559
            }
 
560
 
 
561
            new_id = next_free_rule_id++;
 
562
 
 
563
            new_json_rule = json_object();
 
564
            new_json_pattern = json_object();
 
565
 
 
566
            orig_json_pattern = json_object_get(orig_json_rule, "pattern");
 
567
            if (json_is_object(orig_json_pattern)) {
 
568
                // pattern object
 
569
                json_t *orig_json_feature, *orig_json_matches;
 
570
                orig_json_feature = json_object_get(orig_json_pattern, "feature");
 
571
                if (!json_is_string(orig_json_feature)) {
 
572
                    json_decref(new_json_rule);
 
573
                    json_decref(new_json_pattern);
 
574
                    goto done;
 
575
                }
 
576
                orig_json_matches = json_object_get(orig_json_pattern, "matches");
 
577
                if (!json_is_string(orig_json_matches)) {
 
578
                    json_decref(new_json_rule);
 
579
                    json_decref(new_json_pattern);
 
580
                    goto done;
 
581
                }
 
582
                json_object_set(new_json_pattern, "feature", orig_json_feature);
 
583
                json_object_set(new_json_pattern, "matches", orig_json_matches);
 
584
            } else if (json_is_string(orig_json_pattern)) {
 
585
                // procname
 
586
                json_object_set_new(new_json_pattern, "feature", json_string("procname"));
 
587
                json_object_set(new_json_pattern, "matches", orig_json_pattern);
 
588
            } else {
 
589
                json_decref(new_json_rule);
 
590
                json_decref(new_json_pattern);
 
591
                goto done;
 
592
            }
 
593
 
 
594
            json_object_set_new(new_json_rule, "pattern", new_json_pattern);
 
595
 
 
596
            orig_json_profile = json_object_get(orig_json_rule, "profile");
 
597
            if (json_is_object(orig_json_profile) || json_is_array(orig_json_profile)) {
 
598
                // inline profile object
 
599
                json_t *new_json_profile;
 
600
                json_t *orig_json_settings, *orig_json_name;
 
601
                json_t *new_json_settings;
 
602
 
 
603
                if (json_is_object(orig_json_profile)) {
 
604
                    orig_json_settings = json_object_get(orig_json_profile, "settings");
 
605
                    orig_json_name = json_object_get(orig_json_profile, "name");
 
606
                } else {
 
607
                    // must be array
 
608
                    orig_json_settings = orig_json_profile;
 
609
                    orig_json_name = NULL;
 
610
                }
 
611
 
 
612
                if (json_is_string(orig_json_name)) {
 
613
                    profile_name = app_profile_config_unique_profile_name(config,
 
614
                                                                          json_string_value(orig_json_name),
 
615
                                                                          filename,
 
616
                                                                          TRUE,
 
617
                                                                          &dirty);
 
618
                } else if (!orig_json_name) {
 
619
                    char *profile_name_template;
 
620
                    profile_name_template = nvasprintf("inline_%d", new_id);
 
621
                    profile_name = app_profile_config_unique_profile_name(config,
 
622
                                                                          profile_name_template,
 
623
                                                                          filename,
 
624
                                                                          FALSE,
 
625
                                                                          &dirty);
 
626
                    free(profile_name_template);
 
627
                } else {
 
628
                    json_decref(new_json_rule);
 
629
                    goto done;
 
630
                }
 
631
 
 
632
                new_json_settings = json_settings_parse(orig_json_settings, filename); 
 
633
 
 
634
                if (!profile_name || !new_json_settings) {
 
635
                    free(profile_name);
 
636
                    json_decref(new_json_settings);
 
637
                    json_decref(new_json_rule);
 
638
                    goto done;
 
639
                }
 
640
 
 
641
                new_json_profile = json_object();
 
642
                json_object_set_new(new_json_profile, "settings", new_json_settings);
 
643
 
 
644
                json_object_set_new(new_json_profiles, profile_name, new_json_profile);
 
645
            } else if (json_is_string(orig_json_profile)) {
 
646
                // named profile
 
647
                profile_name = strdup(json_string_value(orig_json_profile));
 
648
            } else {
 
649
                json_decref(new_json_rule);
 
650
                goto done;
 
651
            }
 
652
 
 
653
            json_object_set_new(new_json_rule, "profile", json_string(profile_name));
 
654
            free(profile_name);
 
655
 
 
656
            json_object_set_new(new_json_rule, "id", json_integer(new_id));
 
657
 
 
658
            json_array_append_new(new_json_rules, new_json_rule);
 
659
        }
 
660
    }
 
661
 
 
662
    json_object_set(new_file, "profiles", new_json_profiles);
 
663
    json_object_set(new_file, "rules", new_json_rules);
 
664
    json_object_set_new(new_file, "dirty", dirty ? json_true() : json_false());
 
665
    json_object_set_new(new_file, "new", json_false());
 
666
 
 
667
    // Don't use the atime in the stat_buf; instead measure it here
 
668
    json_object_set_new(new_file, "atime", json_integer(time(NULL)));
 
669
 
 
670
    // If we didn't fail anywhere above, add the file to our configuration
 
671
    app_profile_config_insert_file_object(config, new_file);
 
672
 
 
673
    // Add the profiles in this file to the global profiles list
 
674
    {
 
675
        const char *key;
 
676
        json_t *value;
 
677
        json_object_foreach(new_json_profiles, key, value) {
 
678
            json_object_set_new(config->profile_locations, key, json_string(filename));
 
679
        }
 
680
    }
 
681
 
 
682
    // Add the rules in this file to the global rules list
 
683
    size = json_array_size(new_json_rules);
 
684
    for (i = 0; i < size; i++) {
 
685
        char *key;
 
686
        json_t *new_rule;
 
687
 
 
688
        new_rule = json_array_get(new_json_rules, i);
 
689
        key = rule_id_to_key_string(json_integer_value(json_object_get(new_rule, "id")));
 
690
        json_object_set_new(config->rule_locations, key, json_string(filename));
 
691
        free(key);
 
692
    }
 
693
    config->next_free_rule_id = next_free_rule_id;
 
694
 
 
695
done:
 
696
    json_decref(orig_file);
 
697
    json_decref(new_file);
 
698
    json_decref(new_json_rules);
 
699
    json_decref(new_json_profiles);
 
700
    free(json_text);
 
701
    free(orig_text);
 
702
}
 
703
 
 
704
// Load app profile settings from a directory
 
705
static void app_profile_config_load_files_from_directory(AppProfileConfig *config,
 
706
                                                         const char *dirname)
 
707
{
 
708
    FILE *fp;
 
709
    struct stat stat_buf;
 
710
    struct dirent **namelist;
 
711
    int i, n, ret;
 
712
 
 
713
    n = scandir(dirname, &namelist, NULL, alphasort);
 
714
 
 
715
    if (n < 0) {
 
716
        nv_error_msg("Failed to open directory \"%s\"", dirname);
 
717
        return;
 
718
    }
 
719
 
 
720
    for (i = 0; i < n; i++) {
 
721
        char *d_name = namelist[i]->d_name;
 
722
        char *full_path;
 
723
 
 
724
        // Skip "." and ".."
 
725
        if ((d_name[0] == '.') &&
 
726
            ((d_name[1] == '\0') ||
 
727
             ((d_name[1] == '.') && (d_name[2] == '\0')))) {
 
728
            continue;
 
729
        }
 
730
 
 
731
        full_path = nvstrcat(dirname, "/", d_name, NULL);
 
732
        ret = open_and_stat(full_path, "r", &fp, &stat_buf);
 
733
 
 
734
        if (ret < 0) {
 
735
            free(full_path);
 
736
            free(namelist[i]);
 
737
            continue;
 
738
        }
 
739
 
 
740
        app_profile_config_load_file(config,
 
741
                                     full_path,
 
742
                                     &stat_buf,
 
743
                                     fp);
 
744
        fclose(fp);
 
745
        free(full_path);
 
746
        free(namelist[i]);
 
747
    }
 
748
 
 
749
    free(namelist);
 
750
}
 
751
 
 
752
static json_t *app_profile_config_load_global_options(const char *global_config_file)
 
753
{
 
754
    json_error_t error;
 
755
    json_t *options = json_object();
 
756
    int ret;
 
757
    FILE *fp;
 
758
    struct stat stat_buf;
 
759
    char *option_text;
 
760
    json_t *options_from_file;
 
761
    json_t *option;
 
762
 
 
763
    // By default, app profiles are enabled
 
764
    json_object_set_new(options, "enabled", json_true());
 
765
 
 
766
    if (!global_config_file) {
 
767
        return options;
 
768
    }
 
769
 
 
770
    ret = open_and_stat(global_config_file, "r", &fp, &stat_buf);
 
771
    if ((ret < 0) || !S_ISREG(stat_buf.st_mode)) {
 
772
        return options;
 
773
    }
 
774
 
 
775
    option_text = slurp(fp);
 
776
    fclose(fp);
 
777
 
 
778
    options_from_file = json_loads(option_text, 0, &error);
 
779
    free(option_text);
 
780
 
 
781
    if (!options_from_file) {
 
782
        nv_error_msg("App profile parse error in %s: %s on %s, line %d\n",
 
783
                     global_config_file, error.text, error.source, error.line);
 
784
        return options;
 
785
    }
 
786
 
 
787
    // Load the "enabled" option
 
788
    option = json_object_get(options_from_file, "enabled");
 
789
    if (option && (json_is_true(option) || json_is_false(option))) {
 
790
        json_object_set(options, "enabled", option);
 
791
    }
 
792
 
 
793
    json_decref(options_from_file);
 
794
 
 
795
    return options;
 
796
}
 
797
 
 
798
AppProfileConfig *nv_app_profile_config_load(const char *global_config_file,
 
799
                                             char **search_path,
 
800
                                             size_t search_path_count)
 
801
{
 
802
    size_t i;
 
803
    AppProfileConfig *config = malloc(sizeof(AppProfileConfig));
 
804
 
 
805
    if (!config) {
 
806
        return NULL;
 
807
    }
 
808
 
 
809
    // Initialize the config
 
810
    config->next_free_rule_id = 0;
 
811
 
 
812
    config->parsed_files = json_array();
 
813
    config->profile_locations = json_object();
 
814
    config->rule_locations = json_object();
 
815
 
 
816
    if (global_config_file) {
 
817
        config->global_config_file = nvstrdup(global_config_file);
 
818
    } else {
 
819
        config->global_config_file = NULL;
 
820
    }
 
821
 
 
822
    config->global_options = app_profile_config_load_global_options(global_config_file);
 
823
 
 
824
    config->search_path = malloc(sizeof(char *) * search_path_count);
 
825
    config->search_path_count = search_path_count;
 
826
 
 
827
    for (i = 0; i < search_path_count; i++) {
 
828
        config->search_path[i] = strdup(search_path[i]);
 
829
    }
 
830
 
 
831
    for (i = 0; i < search_path_count; i++) {
 
832
        int ret;
 
833
        struct stat stat_buf;
 
834
        const char *filename = search_path[i];
 
835
        FILE *fp;
 
836
 
 
837
        ret = open_and_stat(filename, "r", &fp, &stat_buf);
 
838
        if (ret < 0) {
 
839
            continue;
 
840
        }
 
841
 
 
842
        if (S_ISDIR(stat_buf.st_mode)) {
 
843
            // Parse files in the directory
 
844
            fclose(fp);
 
845
            app_profile_config_load_files_from_directory(config, filename);
 
846
        } else {
 
847
            // Load the individual file
 
848
            app_profile_config_load_file(config, filename, &stat_buf, fp);
 
849
            fclose(fp);
 
850
            continue;
 
851
        }
 
852
    }
 
853
 
 
854
    return config;
 
855
}
 
856
 
 
857
static int file_in_search_path(AppProfileConfig *config, const char *filename)
 
858
{
 
859
    size_t i;
 
860
    for (i = 0; i < config->search_path_count; i++) {
 
861
        if (!strcmp(filename, config->search_path[i])) {
 
862
            return TRUE;
 
863
        }
 
864
    }
 
865
 
 
866
    return FALSE;
 
867
}
 
868
 
 
869
// Print an error message and optionally capture the error string for later use
 
870
// Note: this assumes fmt is a string literal!
 
871
#define LOG_ERROR(error_str, fmt, ...) do {                   \
 
872
    if (error_str) {                                          \
 
873
        nv_append_sprintf(error_str, fmt "\n", __VA_ARGS__);  \
 
874
    }                                                         \
 
875
    nv_error_msg(fmt, __VA_ARGS__);                           \
 
876
} while (0)
 
877
 
 
878
/*
 
879
 * Creates parent directories as needed, similarly to "mkdir -p"
 
880
 */
 
881
static int nv_mkdirp(const char *dirname, char **error_str)
 
882
{
 
883
    int ret = 0;
 
884
    char *parent_name;
 
885
    const char *cur, *next;
 
886
    struct stat stat_buf;
 
887
    cur = dirname;
 
888
 
 
889
    while (*cur && (next = strchr(cur + 1, '/'))) {
 
890
        parent_name = nvstrndup(dirname, next - dirname);
 
891
        ret = mkdir(parent_name, 0777);
 
892
        if ((ret < 0) && (errno != EEXIST)) {
 
893
            LOG_ERROR(error_str,
 
894
                      "Could not create parent directory \"%s\" "
 
895
                      "for full path \"%s\" (%s)",
 
896
                      parent_name, dirname, strerror(errno));
 
897
            free(parent_name);
 
898
            return ret;
 
899
        }
 
900
        cur = next;
 
901
        free(parent_name);
 
902
    }
 
903
 
 
904
    ret = mkdir(dirname, 0777);
 
905
    if (ret < 0) {
 
906
        if (errno != EEXIST) {
 
907
            LOG_ERROR(error_str, "Could not create directory \"%s\" (%s)",
 
908
                      dirname, strerror(errno));
 
909
        } else {
 
910
            ret = stat(dirname, &stat_buf);
 
911
            if (ret == 0) {
 
912
                if (!S_ISDIR(stat_buf.st_mode)) {
 
913
                    LOG_ERROR(error_str, "Could not create directory \"%s\" "
 
914
                              "(file exists, but not as a directory)",
 
915
                              dirname);
 
916
                    ret = -1;
 
917
                }
 
918
            }
 
919
        }
 
920
    }
 
921
 
 
922
    return ret;
 
923
}
 
924
 
 
925
char *nv_app_profile_config_get_backup_filename(AppProfileConfig *config, const char *filename)
 
926
{
 
927
    char *basename = NULL;
 
928
    char *dirname = NULL;
 
929
    char *backup_name = NULL;
 
930
 
 
931
    if ((config->global_config_file &&
 
932
         !strcmp(config->global_config_file, filename)) ||
 
933
        file_in_search_path(config, filename)) {
 
934
        // Files in the top-level search path, and the global config file, can
 
935
        // be renamed from "$FILE" to "$FILE.backup" without affecting the
 
936
        // configuration
 
937
        backup_name = nvasprintf("%s.backup", filename);
 
938
    } else {
 
939
        // Files in a search path directory *cannot* be renamed from "$FILE" to
 
940
        // "$FILE.backup" without affecting the configuration due to the search
 
941
        // path rules. Instead, attempt to move them to a subdirectory called
 
942
        // ".backup".
 
943
        dirname = nv_dirname(filename);
 
944
        basename = nv_basename(filename);
 
945
        assert(file_in_search_path(config, dirname));
 
946
        backup_name = nvasprintf("%s/.backup/%s", dirname, basename);
 
947
    }
 
948
 
 
949
    free(dirname);
 
950
    free(basename);
 
951
    return backup_name;
 
952
}
 
953
 
 
954
static int app_profile_config_backup_file(AppProfileConfig *config,
 
955
                                          const char *filename,
 
956
                                          char **error_str)
 
957
{
 
958
    int ret;
 
959
    char *backup_name = nv_app_profile_config_get_backup_filename(config, filename);
 
960
    char *backup_dirname = nv_dirname(backup_name);
 
961
 
 
962
    ret = nv_mkdirp(backup_dirname, error_str);
 
963
    if (ret < 0) {
 
964
        LOG_ERROR(error_str, "Could not create backup directory \"%s\" (%s)",
 
965
                  backup_name, strerror(errno));
 
966
        goto done;
 
967
    }
 
968
 
 
969
    ret = rename(filename, backup_name);
 
970
    if (ret < 0) {
 
971
        if (errno == ENOENT) {
 
972
            // Clear the error; the file does not exist
 
973
            ret = 0;
 
974
        } else {
 
975
            LOG_ERROR(error_str, "Could not rename file \"%s\" to \"%s\" for backup (%s)",
 
976
                      filename, backup_name, strerror(errno));
 
977
        }
 
978
    }
 
979
 
 
980
    nv_info_msg("", "Backing up configuration file \"%s\" as \"%s\"\n", filename, backup_name);
 
981
 
 
982
done:
 
983
    free(backup_dirname);
 
984
    free(backup_name);
 
985
    return ret;
 
986
}
 
987
 
 
988
 
 
989
static int app_profile_config_save_updates_to_file(AppProfileConfig *config,
 
990
                                                   const char *filename,
 
991
                                                   const char *update_text,
 
992
                                                   int backup,
 
993
                                                   char **error_str)
 
994
{
 
995
    int file_is_new = FALSE;
 
996
    struct stat stat_buf;
 
997
    char *dirname = NULL;
 
998
    FILE *fp;
 
999
    int ret;
 
1000
 
 
1001
    ret = stat(filename, &stat_buf);
 
1002
 
 
1003
    if ((ret < 0) && (errno != ENOENT)) {
 
1004
        LOG_ERROR(error_str, "Could not stat file \"%s\" (%s)",
 
1005
                  filename, strerror(errno));
 
1006
        goto done;
 
1007
    } else if ((ret < 0) && (errno == ENOENT)) {
 
1008
        file_is_new = TRUE;
 
1009
        // Check if the prefix is in the search path
 
1010
        dirname = nv_dirname(filename);
 
1011
 
 
1012
        if (file_in_search_path(config, dirname)) {
 
1013
            // This file is in a directory in the search path
 
1014
            ret = stat(dirname, &stat_buf);
 
1015
            if ((ret < 0) && (errno != ENOENT)) {
 
1016
                LOG_ERROR(error_str, "Could not stat file \"%s\" (%s)",
 
1017
                          dirname, strerror(errno));
 
1018
                goto done;
 
1019
            } else if ((ret < 0) && errno == ENOENT) {
 
1020
                // Attempt to create the directory in the search path
 
1021
                ret = nv_mkdirp(dirname, error_str);
 
1022
                if (ret < 0) {
 
1023
                    goto done;
 
1024
                }
 
1025
            } else if (S_ISREG(stat_buf.st_mode)) {
 
1026
                // If the search path entry is currently a regular file,
 
1027
                // unlink it and create a directory instead
 
1028
                if (backup) {
 
1029
                    ret = app_profile_config_backup_file(config, dirname,
 
1030
                                                         error_str);
 
1031
                    if (ret < 0) {
 
1032
                        goto done;
 
1033
                    }
 
1034
                }
 
1035
                ret = unlink(dirname);
 
1036
                if (ret < 0) {
 
1037
                    LOG_ERROR(error_str,
 
1038
                              "Could not remove the file \"%s\" (%s)",
 
1039
                              dirname, strerror(errno));
 
1040
                    goto done;
 
1041
                }
 
1042
                ret = nv_mkdirp(dirname, error_str);
 
1043
                if (ret < 0) {
 
1044
                    goto done;
 
1045
                }
 
1046
            }
 
1047
        } else {
 
1048
            // Attempt to create parent directories for this file
 
1049
            ret = nv_mkdirp(dirname, error_str);
 
1050
            if (ret < 0) {
 
1051
                goto done;
 
1052
            }
 
1053
        }
 
1054
    } else if (!S_ISREG(stat_buf.st_mode)) {
 
1055
        // XXX: if this is a directory, we could recursively remove files here,
 
1056
        // but that seems a little dangerous. Instead, complain and bail out
 
1057
        // here.
 
1058
        ret = -1;
 
1059
        LOG_ERROR(error_str,
 
1060
                  "Refusing to write to file \"%s\" "
 
1061
                  "since it is not a regular file", filename);
 
1062
        goto done;
 
1063
    }
 
1064
 
 
1065
    if (!file_is_new && backup) {
 
1066
        ret = app_profile_config_backup_file(config, filename,
 
1067
                                             error_str);
 
1068
        if (ret < 0) {
 
1069
            goto done;
 
1070
        }
 
1071
    }
 
1072
    ret = open_and_stat(filename, "w", &fp, &stat_buf);
 
1073
    if (ret < 0) {
 
1074
        LOG_ERROR(error_str, "Could not write to the file \"%s\" (%s)",
 
1075
                  filename, strerror(errno));
 
1076
        goto done;
 
1077
    }
 
1078
    nv_info_msg("", "Writing to configuration file \"%s\"\n", filename);
 
1079
    fprintf(fp, "%s\n", update_text);
 
1080
    fclose(fp);
 
1081
 
 
1082
done:
 
1083
    free(dirname);
 
1084
    return ret;
 
1085
}
 
1086
 
 
1087
int nv_app_profile_config_save_updates(AppProfileConfig *config,
 
1088
                                       json_t *updates,
 
1089
                                       int backup,
 
1090
                                       char **error_str)
 
1091
{
 
1092
    json_t *update;
 
1093
    const char *filename;
 
1094
    const char *update_text;
 
1095
    size_t i, size;
 
1096
    int ret = 0;
 
1097
    int all_ret = 0;
 
1098
 
 
1099
    if (error_str) {
 
1100
        *error_str = NULL;
 
1101
    }
 
1102
 
 
1103
    for (i = 0, size = json_array_size(updates); i < size; i++) {
 
1104
        update = json_array_get(updates, i);
 
1105
        filename = json_string_value(json_object_get(update, "filename"));
 
1106
        update_text = json_string_value(json_object_get(update, "text"));
 
1107
        ret = app_profile_config_save_updates_to_file(config,
 
1108
                                                      filename,
 
1109
                                                      update_text,
 
1110
                                                      backup,
 
1111
                                                      error_str);
 
1112
        if (ret < 0) {
 
1113
            all_ret = -1;
 
1114
        }
 
1115
    }
 
1116
 
 
1117
    assert(all_ret <= 0);
 
1118
 
 
1119
    // This asserts an error string is set iff we are returning an error
 
1120
    assert(!error_str ||
 
1121
           (!(*error_str) && (all_ret == 0)) ||
 
1122
           ((*error_str) && (all_ret < 0)));
 
1123
 
 
1124
    return all_ret;
 
1125
}
 
1126
 
 
1127
AppProfileConfig *nv_app_profile_config_dup(AppProfileConfig *config)
 
1128
{
 
1129
    size_t i;
 
1130
    AppProfileConfig *new_config;
 
1131
 
 
1132
    new_config = malloc(sizeof(AppProfileConfig));
 
1133
    new_config->parsed_files = json_deep_copy(config->parsed_files);
 
1134
    new_config->profile_locations = json_deep_copy(config->profile_locations);
 
1135
    new_config->rule_locations = json_deep_copy(config->rule_locations);
 
1136
    new_config->next_free_rule_id = config->next_free_rule_id;
 
1137
 
 
1138
    new_config->global_config_file =
 
1139
        config->global_config_file ? strdup(config->global_config_file) : NULL;
 
1140
    new_config->global_options = json_deep_copy(config->global_options);
 
1141
 
 
1142
    new_config->search_path = malloc(sizeof(char *) * config->search_path_count);
 
1143
    new_config->search_path_count = config->search_path_count;
 
1144
 
 
1145
    for (i = 0; i < config->search_path_count; i++) {
 
1146
        new_config->search_path[i] = strdup(config->search_path[i]);
 
1147
    }
 
1148
 
 
1149
    return new_config;
 
1150
}
 
1151
 
 
1152
void nv_app_profile_config_set_enabled(AppProfileConfig *config,
 
1153
                                       int enabled)
 
1154
{
 
1155
    json_t *global_options = config->global_options;
 
1156
 
 
1157
    json_object_set_new(global_options, "enabled",
 
1158
                        enabled ? json_true() : json_false());
 
1159
}
 
1160
 
 
1161
int nv_app_profile_config_get_enabled(AppProfileConfig *config)
 
1162
{
 
1163
    json_t *global_options = config->global_options;
 
1164
    json_t *enabled_json;
 
1165
 
 
1166
    enabled_json = json_object_get(global_options, "enabled");
 
1167
    assert(enabled_json);
 
1168
 
 
1169
    return json_is_true(enabled_json);
 
1170
}
 
1171
 
 
1172
void nv_app_profile_config_free(AppProfileConfig *config)
 
1173
{
 
1174
    size_t i;
 
1175
    json_decref(config->global_options);
 
1176
    json_decref(config->parsed_files);
 
1177
    json_decref(config->profile_locations);
 
1178
    json_decref(config->rule_locations);
 
1179
 
 
1180
    for (i = 0; i < config->search_path_count; i++) {
 
1181
        free(config->search_path[i]);
 
1182
    }
 
1183
 
 
1184
    free(config->search_path);
 
1185
    free(config->global_config_file);
 
1186
 
 
1187
    free(config);
 
1188
}
 
1189
 
 
1190
static json_t *app_profile_config_lookup_file(AppProfileConfig *config, const char *filename)
 
1191
{
 
1192
    size_t i, size;
 
1193
    json_t *json_file, *json_filename;
 
1194
 
 
1195
    size = json_array_size(config->parsed_files);
 
1196
 
 
1197
    for (i = 0; i < size; i++) {
 
1198
        json_file = json_array_get(config->parsed_files, i);
 
1199
        json_filename = json_object_get(json_file, "filename");
 
1200
        if (!strcmp(json_string_value(json_filename), filename)) {
 
1201
            return json_file;
 
1202
        }
 
1203
    }
 
1204
 
 
1205
    return NULL;
 
1206
}
 
1207
 
 
1208
static void app_profile_config_delete_file(AppProfileConfig *config, const char *filename)
 
1209
{
 
1210
    size_t i, size;
 
1211
    json_t *json_file, *json_filename;
 
1212
 
 
1213
    size = json_array_size(config->parsed_files);
 
1214
 
 
1215
    for (i = 0; i < size; i++) {
 
1216
        json_file = json_array_get(config->parsed_files, i);
 
1217
        json_filename = json_object_get(json_file, "filename");
 
1218
        if (!strcmp(json_string_value(json_filename), filename)) {
 
1219
            json_array_remove(config->parsed_files, i);
 
1220
            return;
 
1221
        }
 
1222
    }
 
1223
}
 
1224
 
 
1225
static void app_profile_config_get_per_file_config(AppProfileConfig *config,
 
1226
                                                   const char *filename,
 
1227
                                                   json_t **file,
 
1228
                                                   json_t **rules,
 
1229
                                                   json_t **profiles)
 
1230
{
 
1231
    *file = app_profile_config_lookup_file(config, filename);
 
1232
 
 
1233
    if (!*file) {
 
1234
        *rules = NULL;
 
1235
        *profiles = NULL;
 
1236
    } else {
 
1237
        *rules = json_object_get(*file, "rules");
 
1238
        *profiles = json_object_get(*file, "profiles");
 
1239
    }
 
1240
}
 
1241
 
 
1242
/*
 
1243
 * Convert the internal representation of an application profile to a
 
1244
 * representation suitable for writing to disk.
 
1245
 */
 
1246
static json_t *app_profile_config_profile_output(const char *profile_name, const json_t *orig_profile)
 
1247
{
 
1248
    json_t *new_profile = json_object();
 
1249
 
 
1250
    json_object_set_new(new_profile, "name", json_string(profile_name));
 
1251
    json_object_set(new_profile, "settings", json_object_get(orig_profile, "settings"));
 
1252
 
 
1253
    return new_profile;
 
1254
}
 
1255
 
 
1256
static json_t *app_profile_config_rule_output(const json_t *orig_rule)
 
1257
{
 
1258
    json_t *new_rule = json_object();
 
1259
 
 
1260
    json_object_set(new_rule, "pattern", json_object_get(orig_rule, "pattern"));
 
1261
    json_object_set(new_rule, "profile", json_object_get(orig_rule, "profile"));
 
1262
 
 
1263
    return new_rule;
 
1264
}
 
1265
 
 
1266
static char *config_to_cfg_file_syntax(json_t *old_rules, json_t *old_profiles)
 
1267
{
 
1268
    char *output = NULL;
 
1269
    const char *profile_name;
 
1270
    json_t *root, *rules_array, *profiles_array;
 
1271
    json_t *old_rule, *old_profile;
 
1272
    json_t *new_rule, *new_profile;
 
1273
    size_t i, size;
 
1274
 
 
1275
    root = json_object();
 
1276
    if (!root) {
 
1277
        goto fail;
 
1278
    }
 
1279
 
 
1280
    rules_array = json_array();
 
1281
    if (!rules_array) {
 
1282
        goto fail;
 
1283
    }
 
1284
    json_object_set_new(root, "rules", rules_array);
 
1285
 
 
1286
    profiles_array = json_array();
 
1287
    if (!profiles_array) {
 
1288
        goto fail;
 
1289
    }
 
1290
    json_object_set_new(root, "profiles", profiles_array);
 
1291
 
 
1292
    if (old_rules) {
 
1293
        size = json_array_size(old_rules);
 
1294
        for (i = 0; i < size; i++) {
 
1295
            old_rule = json_array_get(old_rules, i);
 
1296
            new_rule = app_profile_config_rule_output(old_rule);
 
1297
            json_array_append_new(rules_array, new_rule);
 
1298
        }
 
1299
    }
 
1300
 
 
1301
    if (old_profiles) {
 
1302
        json_object_foreach(old_profiles, profile_name, old_profile) {
 
1303
            new_profile = app_profile_config_profile_output(profile_name, old_profile);
 
1304
            json_array_append_new(profiles_array, new_profile);
 
1305
        }
 
1306
    }
 
1307
 
 
1308
    output = json_dumps(root, JSON_ENSURE_ASCII | JSON_INDENT(4));
 
1309
 
 
1310
fail:
 
1311
    json_decref(root);
 
1312
    return output;
 
1313
}
 
1314
 
 
1315
static void add_files_from_config(AppProfileConfig *config, json_t *all_files, json_t *changed_files)
 
1316
{
 
1317
    json_t *file, *filename;
 
1318
    size_t i, size;
 
1319
    for (i = 0, size = json_array_size(config->parsed_files); i < size; i++) {
 
1320
        file = json_array_get(config->parsed_files, i);
 
1321
        filename = json_object_get(file, "filename");
 
1322
        json_object_set_new(all_files, json_string_value(filename), json_true());
 
1323
        if (json_is_true(json_object_get(file, "dirty"))) {
 
1324
            json_object_set_new(changed_files, json_string_value(filename), json_true());
 
1325
        }
 
1326
    }
 
1327
}
 
1328
 
 
1329
static json_t *app_profile_config_validate_global_options(AppProfileConfig *new_config,
 
1330
                                                          AppProfileConfig *old_config)
 
1331
{
 
1332
    json_t *update = NULL;
 
1333
    char *option_text;
 
1334
 
 
1335
    assert((!new_config->global_config_file && !old_config->global_config_file) ||
 
1336
           (!strcmp(new_config->global_config_file, old_config->global_config_file)));
 
1337
 
 
1338
    if (new_config->global_config_file &&
 
1339
        !json_equal(new_config->global_options, old_config->global_options)) {
 
1340
        update = json_object();
 
1341
        json_object_set_new(update, "filename", json_string(new_config->global_config_file));
 
1342
        option_text = json_dumps(new_config->global_options, JSON_ENSURE_ASCII | JSON_INDENT(4));
 
1343
        json_object_set_new(update, "text", json_string(option_text));
 
1344
        free(option_text);
 
1345
    }
 
1346
 
 
1347
    return update;
 
1348
}
 
1349
 
 
1350
json_t *nv_app_profile_config_validate(AppProfileConfig *new_config,
 
1351
                                       AppProfileConfig *old_config)
 
1352
{
 
1353
    json_t *all_files, *changed_files;
 
1354
    json_t *new_file, *new_rules, *old_rules;
 
1355
    json_t *old_file, *new_profiles, *old_profiles;
 
1356
    json_t *updates, *update;
 
1357
    const char *filename;
 
1358
    char *update_text;
 
1359
    json_t *unused;
 
1360
 
 
1361
    updates = json_array();
 
1362
 
 
1363
    // Determine if the global config file needs to be updated
 
1364
    update = app_profile_config_validate_global_options(new_config, old_config);
 
1365
    if (update) {
 
1366
        json_array_append_new(updates, update);
 
1367
    }
 
1368
 
 
1369
    // Build a set of files to examine: this is the union of files specified
 
1370
    // by the old configuration and the new.
 
1371
    all_files = json_object();
 
1372
    changed_files = json_object();
 
1373
    add_files_from_config(new_config, all_files, changed_files);
 
1374
    add_files_from_config(old_config, all_files, changed_files);
 
1375
 
 
1376
    // For each file in the set, determine if it needs to be updated
 
1377
    json_object_foreach(all_files, filename, unused) {
 
1378
        app_profile_config_get_per_file_config(new_config, filename, &new_file, &new_rules, &new_profiles);
 
1379
        app_profile_config_get_per_file_config(old_config, filename, &old_file, &old_rules, &old_profiles);
 
1380
 
 
1381
        // Simply compare the JSON objects
 
1382
        if (!json_equal(old_rules, new_rules) || !json_equal(old_profiles, new_profiles)) {
 
1383
            json_object_set_new(changed_files, filename, json_true());
 
1384
        }
 
1385
    }
 
1386
 
 
1387
    // For each file that changed, generate an update record with the new JSON
 
1388
    json_object_foreach(changed_files, filename, unused) {
 
1389
        update = json_object();
 
1390
 
 
1391
        json_object_set_new(update, "filename", json_string(filename));
 
1392
        app_profile_config_get_per_file_config(new_config, filename, &new_file, &new_rules, &new_profiles);
 
1393
 
 
1394
        update_text = config_to_cfg_file_syntax(new_rules, new_profiles);
 
1395
        json_object_set_new(update, "text", json_string(update_text));
 
1396
 
 
1397
        json_array_append_new(updates, update);
 
1398
        free(update_text);
 
1399
    }
 
1400
 
 
1401
    json_decref(all_files);
 
1402
    json_decref(changed_files);
 
1403
 
 
1404
    return updates;
 
1405
}
 
1406
 
 
1407
static int file_object_is_empty(const json_t *file)
 
1408
{
 
1409
    const json_t *rules;
 
1410
    const json_t *profiles;
 
1411
 
 
1412
    rules = json_object_get(file, "rules");
 
1413
    profiles = json_object_get(file, "profiles");
 
1414
 
 
1415
    return (!json_array_size(rules) && !json_object_size(profiles));
 
1416
}
 
1417
 
 
1418
/*
 
1419
 * Checks whether the given file is "empty" (contains no rules and profiles)
 
1420
 * and "new" (created in the configuration and not loaded from disk), and
 
1421
 * removes it from the configuration if both criteria are satisfied.
 
1422
 */
 
1423
static void app_profile_config_prune_empty_file(AppProfileConfig *config, const json_t *file)
 
1424
{
 
1425
    char *filename;
 
1426
    if (json_is_true(json_object_get(file, "new")) && file_object_is_empty(file)) {
 
1427
        filename = strdup(json_string_value(json_object_get(file, "filename")));
 
1428
        app_profile_config_delete_file(config, filename);
 
1429
        free(filename);
 
1430
    }
 
1431
}
 
1432
 
 
1433
int nv_app_profile_config_update_profile(AppProfileConfig *config,
 
1434
                                         const char *filename,
 
1435
                                         const char *profile_name,
 
1436
                                         json_t *new_profile)
 
1437
{
 
1438
    json_t *file;
 
1439
    json_t *old_file = NULL;
 
1440
    json_t *file_profiles;
 
1441
    const char *old_filename;
 
1442
 
 
1443
    old_filename = json_string_value(json_object_get(config->profile_locations, profile_name));
 
1444
 
 
1445
    if (old_filename) {
 
1446
        // Existing profile
 
1447
        old_file = app_profile_config_lookup_file(config, old_filename);
 
1448
        assert(old_file);
 
1449
    }
 
1450
 
 
1451
    // If there is an existing profile with a differing filename, delete it first
 
1452
    if (old_filename && (strcmp(filename, old_filename) != 0)) {
 
1453
        file = app_profile_config_lookup_file(config, old_filename);
 
1454
        file_profiles = json_object_get(file, "profiles");
 
1455
        if (file) {
 
1456
            json_object_del(file_profiles, profile_name);
 
1457
        }
 
1458
    }
 
1459
 
 
1460
    file = app_profile_config_lookup_file(config, filename);
 
1461
    if (!file) {
 
1462
        file = app_profile_config_new_file(config, filename);
 
1463
    }
 
1464
 
 
1465
    file_profiles = json_object_get(file, "profiles");
 
1466
    json_object_set(file_profiles, profile_name, new_profile);
 
1467
    json_object_set(config->profile_locations, profile_name, json_string(filename));
 
1468
 
 
1469
    if (old_file) {
 
1470
        app_profile_config_prune_empty_file(config, old_file);
 
1471
    }
 
1472
 
 
1473
    return !old_filename;
 
1474
}
 
1475
 
 
1476
void nv_app_profile_config_delete_profile(AppProfileConfig *config,
 
1477
                                          const char *profile_name)
 
1478
{
 
1479
    json_t *file = NULL;
 
1480
    const char *filename = json_string_value(json_object_get(config->profile_locations, profile_name));
 
1481
 
 
1482
    if (filename) {
 
1483
        file = app_profile_config_lookup_file(config, filename);
 
1484
        if (file) {
 
1485
            json_object_del(json_object_get(file, "profiles"), profile_name);
 
1486
        }
 
1487
    }
 
1488
 
 
1489
    json_object_del(config->profile_locations, profile_name);
 
1490
 
 
1491
    if (file) {
 
1492
        app_profile_config_prune_empty_file(config, file);
 
1493
    }
 
1494
}
 
1495
 
 
1496
int nv_app_profile_config_create_rule(AppProfileConfig *config,
 
1497
                                      const char *filename,
 
1498
                                      json_t *new_rule)
 
1499
{
 
1500
    char *key;
 
1501
    json_t *file, *file_rules;
 
1502
    json_t *new_rule_copy;
 
1503
    int new_id;
 
1504
 
 
1505
    file = app_profile_config_lookup_file(config, filename);
 
1506
    if (!file) {
 
1507
        file = app_profile_config_new_file(config, filename);
 
1508
    }
 
1509
 
 
1510
    file_rules = json_object_get(file, "rules");
 
1511
 
 
1512
    // Add the rule to the head of the per-file list
 
1513
    json_array_append(file_rules, new_rule);
 
1514
    new_rule_copy = json_array_get(file_rules, json_array_size(file_rules) - 1);
 
1515
 
 
1516
    new_id = config->next_free_rule_id++;
 
1517
    json_object_set_new(new_rule_copy, "id", json_integer(new_id));
 
1518
 
 
1519
    key = rule_id_to_key_string(new_id);
 
1520
    json_object_set(config->rule_locations, key, json_string(filename));
 
1521
    free(key);
 
1522
 
 
1523
    return new_id;
 
1524
}
 
1525
 
 
1526
static int lookup_rule_index_in_array(json_t *rules, int id)
 
1527
{
 
1528
    json_t *rule, *rule_id;
 
1529
    size_t i, size;
 
1530
    for (i = 0, size = json_array_size(rules); i < size; i++) {
 
1531
        rule = json_array_get(rules, i);
 
1532
        rule_id = json_object_get(rule, "id");
 
1533
        if (json_integer_value(rule_id) == id) {
 
1534
            return i;
 
1535
        }
 
1536
    }
 
1537
 
 
1538
    return -1;
 
1539
}
 
1540
 
 
1541
int nv_app_profile_config_update_rule(AppProfileConfig *config,
 
1542
                                      const char *filename,
 
1543
                                      int id,
 
1544
                                      json_t *new_rule)
 
1545
{
 
1546
    json_t *old_file, *new_file;
 
1547
    json_t *old_file_rules, *new_file_rules;
 
1548
    json_t *new_rule_copy;
 
1549
    const char *old_filename;
 
1550
    char *key;
 
1551
    int idx;
 
1552
    int rule_moved;
 
1553
 
 
1554
    key = rule_id_to_key_string(id);
 
1555
    old_filename = json_string_value(json_object_get(config->rule_locations, key));
 
1556
    assert(old_filename);
 
1557
 
 
1558
    old_file = app_profile_config_lookup_file(config, old_filename);
 
1559
    assert(old_file);
 
1560
 
 
1561
    old_file_rules = json_object_get(old_file, "rules");
 
1562
 
 
1563
    if (filename && (strcmp(filename, old_filename) != 0)) {
 
1564
        // If the rule has a new file, delete the rule and re-add it
 
1565
        new_file = app_profile_config_lookup_file(config, filename);
 
1566
        rule_moved = TRUE;
 
1567
        if (!new_file) {
 
1568
            new_file = app_profile_config_new_file(config, filename);
 
1569
        }
 
1570
 
 
1571
        new_file_rules = json_object_get(new_file, "rules");
 
1572
 
 
1573
        idx = lookup_rule_index_in_array(old_file_rules, id);
 
1574
        if (idx != -1) {
 
1575
            json_array_remove(old_file_rules, idx);
 
1576
        }
 
1577
        json_array_insert(new_file_rules, 0, new_rule);
 
1578
        new_rule_copy = json_array_get(new_file_rules, 0);
 
1579
        json_object_set_new(new_rule_copy, "id", json_integer(id));
 
1580
 
 
1581
        json_object_set_new(config->rule_locations, key, json_string(filename));
 
1582
    } else {
 
1583
        // Otherwise, just edit the existing rule
 
1584
        rule_moved = FALSE;
 
1585
        idx = lookup_rule_index_in_array(old_file_rules, id);
 
1586
        if (idx != -1) {
 
1587
            json_array_set(old_file_rules, idx, new_rule);
 
1588
            new_rule_copy = json_array_get(old_file_rules, idx);
 
1589
            json_object_set_new(new_rule_copy, "id", json_integer(id));
 
1590
        }
 
1591
    }
 
1592
 
 
1593
    free(key);
 
1594
 
 
1595
    app_profile_config_prune_empty_file(config, old_file);
 
1596
 
 
1597
    return rule_moved;
 
1598
}
 
1599
 
 
1600
 
 
1601
void nv_app_profile_config_delete_rule(AppProfileConfig *config, int id)
 
1602
{
 
1603
    json_t *file, *file_rules;
 
1604
    const char *filename;
 
1605
    char *key;
 
1606
    int idx;
 
1607
 
 
1608
    key = rule_id_to_key_string(id);
 
1609
 
 
1610
    filename = json_string_value(json_object_get(config->rule_locations, key));
 
1611
    assert(filename);
 
1612
 
 
1613
    file = app_profile_config_lookup_file(config, filename);
 
1614
    assert(file);
 
1615
 
 
1616
    file_rules = json_object_get(file, "rules");
 
1617
 
 
1618
    idx = lookup_rule_index_in_array(file_rules, id);
 
1619
    if (idx != -1) {
 
1620
        json_array_remove(file_rules, idx);
 
1621
    }
 
1622
 
 
1623
    json_object_del(config->rule_locations, key);
 
1624
    free(key);
 
1625
}
 
1626
 
 
1627
size_t nv_app_profile_config_count_rules(AppProfileConfig *config)
 
1628
{
 
1629
    return json_object_size(config->rule_locations);
 
1630
}
 
1631
 
 
1632
static size_t app_profile_config_count_rules_before(AppProfileConfig *config, const char *filename)
 
1633
{
 
1634
    size_t i, size;
 
1635
    size_t num_rules = 0;
 
1636
    json_t *cur_file, *cur_filename;
 
1637
 
 
1638
    for (i = 0, size = json_array_size(config->parsed_files); i < size; i++) {
 
1639
        cur_file = json_array_get(config->parsed_files, i);
 
1640
        cur_filename = json_object_get(cur_file, "filename");
 
1641
        if (!strcmp(filename, json_string_value(cur_filename))) {
 
1642
            break;
 
1643
        }
 
1644
        num_rules += json_array_size(json_object_get(cur_file, "rules"));
 
1645
    }
 
1646
 
 
1647
    return num_rules;
 
1648
}
 
1649
 
 
1650
static void app_profile_config_insert_rule(AppProfileConfig *config,
 
1651
                                           json_t *rule,
 
1652
                                           size_t new_pri,
 
1653
                                           const char *old_filename)
 
1654
{
 
1655
    size_t i, j, size;
 
1656
    size_t num_rules = 0;
 
1657
    char *key;
 
1658
    const char *filename;
 
1659
    json_t *file, *file_rules;
 
1660
    json_t *target[2];
 
1661
    size_t rules_before_target[2];
 
1662
 
 
1663
    for (i = 0, j = 0, size = json_array_size(config->parsed_files); i < size; i++) {
 
1664
        file = json_array_get(config->parsed_files, i);
 
1665
        file_rules = json_object_get(file, "rules");
 
1666
        if ((num_rules <= new_pri) &&
 
1667
            (num_rules + json_array_size(file_rules) >= new_pri)) {
 
1668
            // Potential target file for this rule
 
1669
            rules_before_target[j] = num_rules;
 
1670
            target[j++] = file;
 
1671
            if (j >= 2) {
 
1672
                break;
 
1673
            }
 
1674
        }
 
1675
        num_rules += json_array_size(file_rules);
 
1676
    }
 
1677
 
 
1678
    assert((j > 0) && (j <= 2));
 
1679
 
 
1680
    // If possible, we prefer to keep the rule in the same file as before
 
1681
    for (i = 0; i < j; i++) {
 
1682
        filename = json_string_value(json_object_get(target[i], "filename"));
 
1683
        if (!strcmp(filename, old_filename)) {
 
1684
            break;
 
1685
        }
 
1686
    }
 
1687
    i = (i == j) ? 0 : i;
 
1688
 
 
1689
    file_rules = json_object_get(target[i], "rules");
 
1690
    json_array_insert_new(file_rules, new_pri - rules_before_target[i], rule);
 
1691
    // Update the hashtable to point to the new file
 
1692
    key = rule_id_to_key_string(json_integer_value(json_object_get(rule, "id")));
 
1693
    filename = json_string_value(json_object_get(target[i], "filename"));
 
1694
    json_object_set_new(config->rule_locations, key, json_string(filename));
 
1695
    free(key);
 
1696
}
 
1697
 
 
1698
size_t nv_app_profile_config_get_rule_priority(AppProfileConfig *config,
 
1699
                                               int id)
 
1700
{
 
1701
    json_t *file, *file_rules;
 
1702
    const char *filename;
 
1703
    int idx;
 
1704
    char *key;
 
1705
 
 
1706
    key = rule_id_to_key_string(id);
 
1707
 
 
1708
    filename = json_string_value(json_object_get(config->rule_locations, key));
 
1709
    assert(filename);
 
1710
 
 
1711
    file = app_profile_config_lookup_file(config, filename);
 
1712
    assert(file);
 
1713
 
 
1714
    file_rules = json_object_get(file, "rules");
 
1715
 
 
1716
    idx = lookup_rule_index_in_array(file_rules, id);
 
1717
 
 
1718
    free(key);
 
1719
 
 
1720
    return app_profile_config_count_rules_before(config, filename) + idx;
 
1721
}
 
1722
 
 
1723
static void app_profile_config_set_abs_rule_priority_internal(AppProfileConfig *config,
 
1724
                                                              int id,
 
1725
                                                              size_t new_pri,
 
1726
                                                              size_t current_pri,
 
1727
                                                              size_t lowest_pri)
 
1728
{
 
1729
    json_t *rule, *rule_copy;
 
1730
    json_t *file, *file_rules;
 
1731
    const char *filename;
 
1732
    int idx;
 
1733
    char *key;
 
1734
 
 
1735
    if (new_pri == current_pri) {
 
1736
        return;
 
1737
    } else if (new_pri >= lowest_pri) {
 
1738
        new_pri = lowest_pri - 1;
 
1739
    }
 
1740
 
 
1741
    key = rule_id_to_key_string(id);
 
1742
 
 
1743
    filename = json_string_value(json_object_get(config->rule_locations, key));
 
1744
    assert(filename);
 
1745
 
 
1746
    file = app_profile_config_lookup_file(config, filename);
 
1747
    assert(file);
 
1748
 
 
1749
    file_rules = json_object_get(file, "rules");
 
1750
    idx = lookup_rule_index_in_array(file_rules, id);
 
1751
    assert(idx >= 0);
 
1752
    rule = json_array_get(file_rules, idx);
 
1753
 
 
1754
    rule_copy = json_deep_copy(rule);
 
1755
    json_array_remove(file_rules, idx);
 
1756
 
 
1757
    app_profile_config_insert_rule(config, rule_copy, new_pri, filename);
 
1758
 
 
1759
    app_profile_config_prune_empty_file(config, file);
 
1760
 
 
1761
    free(key);
 
1762
}
 
1763
 
 
1764
void nv_app_profile_config_set_abs_rule_priority(AppProfileConfig *config,
 
1765
                                                 int id, size_t new_pri)
 
1766
{
 
1767
    size_t current_pri = nv_app_profile_config_get_rule_priority(config, id);
 
1768
    size_t lowest_pri = nv_app_profile_config_count_rules(config);
 
1769
    app_profile_config_set_abs_rule_priority_internal(config, id, new_pri, current_pri, lowest_pri);
 
1770
}
 
1771
 
 
1772
void nv_app_profile_config_change_rule_priority(AppProfileConfig *config,
 
1773
                                                int id,
 
1774
                                                int delta)
 
1775
{
 
1776
    size_t lowest_pri = nv_app_profile_config_count_rules(config);
 
1777
    size_t current_pri = nv_app_profile_config_get_rule_priority(config, id);
 
1778
    size_t new_pri;
 
1779
    if ((delta < 0) && (((size_t)-delta) > current_pri)) {
 
1780
        new_pri = 0;
 
1781
    } else {
 
1782
        new_pri = current_pri + delta;
 
1783
    }
 
1784
    app_profile_config_set_abs_rule_priority_internal(config, id, new_pri, current_pri, lowest_pri);
 
1785
}
 
1786
 
 
1787
const json_t *nv_app_profile_config_get_profile(AppProfileConfig *config,
 
1788
                                                const char *profile_name)
 
1789
{
 
1790
    json_t *file, *file_profiles;
 
1791
    json_t *filename = json_object_get(config->profile_locations, profile_name);
 
1792
 
 
1793
    if (!filename) {
 
1794
        return NULL;
 
1795
    }
 
1796
 
 
1797
    file = app_profile_config_lookup_file(config, json_string_value(filename));
 
1798
    file_profiles = json_object_get(file, "profiles");
 
1799
 
 
1800
    return json_object_get(file_profiles, profile_name);
 
1801
}
 
1802
 
 
1803
 
 
1804
const json_t *nv_app_profile_config_get_rule(AppProfileConfig *config,
 
1805
                                             int id)
 
1806
{
 
1807
    char *key = rule_id_to_key_string(id);
 
1808
    json_t *file, *rule, *filename;
 
1809
    json_t *file_rules;
 
1810
    int idx;
 
1811
 
 
1812
    filename = json_object_get(config->rule_locations, key);
 
1813
 
 
1814
    if (!filename) {
 
1815
        free(key);
 
1816
        return NULL;
 
1817
    }
 
1818
 
 
1819
    file = app_profile_config_lookup_file(config, json_string_value(filename));
 
1820
    file_rules = json_object_get(file, "rules");
 
1821
 
 
1822
    idx = lookup_rule_index_in_array(file_rules, id);
 
1823
    if (idx != -1) {
 
1824
        rule = json_array_get(file_rules, idx);
 
1825
    } else {
 
1826
        assert(0);
 
1827
        rule = NULL;
 
1828
    }
 
1829
 
 
1830
    free(key);
 
1831
    return rule;
 
1832
}
 
1833
 
 
1834
struct AppProfileConfigProfileIterRec {
 
1835
    AppProfileConfig *config;
 
1836
    size_t file_idx;
 
1837
    json_t *profiles;
 
1838
    void *profile_iter;
 
1839
};
 
1840
 
 
1841
AppProfileConfigProfileIter *nv_app_profile_config_profile_iter(AppProfileConfig *config)
 
1842
{
 
1843
    AppProfileConfigProfileIter *iter = malloc(sizeof(AppProfileConfigProfileIter));
 
1844
 
 
1845
    iter->config = config;
 
1846
    iter->file_idx = 0;
 
1847
    iter->profile_iter = NULL;
 
1848
 
 
1849
    return nv_app_profile_config_profile_iter_next(iter);
 
1850
}
 
1851
 
 
1852
AppProfileConfigProfileIter *nv_app_profile_config_profile_iter_next(AppProfileConfigProfileIter *iter)
 
1853
{
 
1854
    AppProfileConfig *config = iter->config;
 
1855
    json_t *file;
 
1856
    int advance = TRUE;
 
1857
    size_t size;
 
1858
 
 
1859
    size = json_array_size(config->parsed_files);
 
1860
    while ((iter->file_idx < size) &&
 
1861
           !iter->profile_iter) {
 
1862
        file = json_array_get(config->parsed_files, iter->file_idx);
 
1863
        iter->profiles = json_object_get(file, "profiles");
 
1864
        iter->profile_iter = json_object_iter(iter->profiles);
 
1865
        iter->file_idx++;
 
1866
        advance = FALSE;
 
1867
    }
 
1868
 
 
1869
    if (!iter->profile_iter) {
 
1870
        free(iter);
 
1871
        return NULL;
 
1872
    }
 
1873
 
 
1874
    if (advance) {
 
1875
        iter->profile_iter = json_object_iter_next(iter->profiles, iter->profile_iter);
 
1876
    }
 
1877
 
 
1878
    while ((iter->file_idx < size) &&
 
1879
           !iter->profile_iter) {
 
1880
        file = json_array_get(config->parsed_files, iter->file_idx);
 
1881
        iter->profiles = json_object_get(file, "profiles");
 
1882
        iter->profile_iter = json_object_iter(iter->profiles);
 
1883
        iter->file_idx++;
 
1884
    }
 
1885
 
 
1886
    if (!iter->profile_iter) {
 
1887
        free(iter);
 
1888
        return NULL;
 
1889
    }
 
1890
 
 
1891
    return iter;
 
1892
}
 
1893
 
 
1894
 
 
1895
struct AppProfileConfigRuleIterRec {
 
1896
    AppProfileConfig *config;
 
1897
    size_t file_idx;
 
1898
    int rule_idx;
 
1899
    json_t *rules;
 
1900
};
 
1901
 
 
1902
AppProfileConfigRuleIter *nv_app_profile_config_rule_iter(AppProfileConfig *config)
 
1903
{
 
1904
    AppProfileConfigRuleIter *iter = malloc(sizeof(AppProfileConfigRuleIter));
 
1905
 
 
1906
    iter->file_idx = 0;
 
1907
    iter->rule_idx = -1;
 
1908
    iter->config = config;
 
1909
 
 
1910
    return nv_app_profile_config_rule_iter_next(iter);
 
1911
}
 
1912
 
 
1913
AppProfileConfigRuleIter *nv_app_profile_config_rule_iter_next(AppProfileConfigRuleIter *iter)
 
1914
{
 
1915
    AppProfileConfig *config = iter->config;
 
1916
    json_t *file;
 
1917
    int advance = TRUE;
 
1918
    size_t size;
 
1919
 
 
1920
    size = json_array_size(config->parsed_files);
 
1921
    while ((iter->file_idx < size) &&
 
1922
           (iter->rule_idx == -1)) {
 
1923
        file = json_array_get(config->parsed_files, iter->file_idx);
 
1924
        iter->rules = json_object_get(file, "rules");
 
1925
        if (json_array_size(iter->rules)) {
 
1926
            iter->rule_idx = 0;
 
1927
        }
 
1928
        iter->file_idx++;
 
1929
        advance = FALSE;
 
1930
    }
 
1931
 
 
1932
    if (iter->rule_idx == -1) {
 
1933
        free(iter);
 
1934
        return NULL;
 
1935
    }
 
1936
 
 
1937
    if (advance) {
 
1938
        iter->rule_idx++;
 
1939
        if (iter->rule_idx >= json_array_size(iter->rules)) {
 
1940
            iter->rule_idx = -1;
 
1941
        }
 
1942
    }
 
1943
 
 
1944
    while ((iter->file_idx < size) &&
 
1945
           (iter->rule_idx == -1)) {
 
1946
        file = json_array_get(config->parsed_files, iter->file_idx);
 
1947
        iter->rules = json_object_get(file, "rules");
 
1948
        if (json_array_size(iter->rules)) {
 
1949
            iter->rule_idx = 0;
 
1950
        }
 
1951
        iter->file_idx++;
 
1952
    }
 
1953
 
 
1954
    if (iter->rule_idx == -1) {
 
1955
        free(iter);
 
1956
        return NULL;
 
1957
    }
 
1958
 
 
1959
    return iter;
 
1960
}
 
1961
 
 
1962
const char *nv_app_profile_config_profile_iter_name(AppProfileConfigProfileIter *iter)
 
1963
{
 
1964
    return json_object_iter_key(iter->profile_iter);
 
1965
}
 
1966
 
 
1967
json_t *nv_app_profile_config_profile_iter_val(AppProfileConfigProfileIter *iter)
 
1968
{
 
1969
    return json_object_iter_value(iter->profile_iter);
 
1970
}
 
1971
 
 
1972
size_t nv_app_profile_config_rule_iter_pri(AppProfileConfigRuleIter *iter)
 
1973
{
 
1974
    json_t *rule = nv_app_profile_config_rule_iter_val(iter);
 
1975
    return nv_app_profile_config_get_rule_priority(iter->config,
 
1976
                json_integer_value(json_object_get(rule, "id")));
 
1977
}
 
1978
 
 
1979
json_t *nv_app_profile_config_rule_iter_val(AppProfileConfigRuleIter *iter)
 
1980
{
 
1981
    return json_array_get(iter->rules, iter->rule_idx);
 
1982
}
 
1983
 
 
1984
const char *nv_app_profile_config_profile_iter_filename(AppProfileConfigProfileIter *iter)
 
1985
{
 
1986
    json_t *file = json_array_get(iter->config->parsed_files, iter->file_idx - 1);
 
1987
    return json_string_value(json_object_get(file, "filename"));
 
1988
}
 
1989
 
 
1990
const char *nv_app_profile_config_rule_iter_filename(AppProfileConfigRuleIter *iter)
 
1991
{
 
1992
    json_t *file = json_array_get(iter->config->parsed_files, iter->file_idx - 1);
 
1993
    return json_string_value(json_object_get(file, "filename"));
 
1994
}
 
1995
 
 
1996
const char *nv_app_profile_config_get_rule_filename(AppProfileConfig *config,
 
1997
                                                    int id)
 
1998
{
 
1999
    const char *filename;
 
2000
    char *key;
 
2001
 
 
2002
    key = rule_id_to_key_string(id);
 
2003
    filename = json_string_value(json_object_get(config->rule_locations, key));
 
2004
    free(key);
 
2005
 
 
2006
    return filename;
 
2007
}
 
2008
 
 
2009
const char *nv_app_profile_config_get_profile_filename(AppProfileConfig *config,
 
2010
                                                       const char *profile_name)
 
2011
{
 
2012
    return json_string_value(json_object_get(config->profile_locations, profile_name));
 
2013
}
 
2014
 
 
2015
static char *get_search_path_string(AppProfileConfig *config)
 
2016
{
 
2017
    size_t i;
 
2018
    char *new_s;
 
2019
    char *s = strdup("");
 
2020
    for (i = 0; i < config->search_path_count; i++) {
 
2021
        new_s = nvasprintf("%s\t\"%s\"\n",
 
2022
                           s, config->search_path[i]);
 
2023
        free(s);
 
2024
        s = new_s;
 
2025
    }
 
2026
 
 
2027
    return s;
 
2028
}
 
2029
 
 
2030
static int app_profile_config_check_is_prefix(const char *filename1, const char *filename2)
 
2031
{
 
2032
    char *dirname1, *dirname2;
 
2033
    int prefix_state;
 
2034
    dirname1 = nv_dirname(filename1);
 
2035
    dirname2 = nv_dirname(filename2);
 
2036
 
 
2037
    if (!strcmp(dirname1, filename2)) {
 
2038
        prefix_state = 1;
 
2039
    } else if (!strcmp(filename1, dirname2)) {
 
2040
        prefix_state = -1;
 
2041
    } else {
 
2042
        prefix_state = 0;
 
2043
    }
 
2044
 
 
2045
    free(dirname1);
 
2046
    free(dirname2);
 
2047
 
 
2048
    return prefix_state;
 
2049
}
 
2050
 
 
2051
int nv_app_profile_config_check_valid_source_file(AppProfileConfig *config,
 
2052
                                                  const char *filename,
 
2053
                                                  char **reason)
 
2054
{
 
2055
    const json_t *parsed_file;
 
2056
    size_t i, size;
 
2057
    const char *cur_filename;
 
2058
    char *dirname;
 
2059
    char *search_path_string;
 
2060
    int prefix_state;
 
2061
 
 
2062
    // Check if the source file can be found in the search path
 
2063
    dirname = NULL;
 
2064
    for (i = 0; i < config->search_path_count; i++) {
 
2065
        if (!strcmp(filename, config->search_path[i])) {
 
2066
            break;
 
2067
        } else {
 
2068
            if (!dirname) {
 
2069
                dirname = nv_dirname(filename);
 
2070
            }
 
2071
            if (!strcmp(dirname, config->search_path[i])) {
 
2072
                break;
 
2073
            }
 
2074
        }
 
2075
    }
 
2076
    free(dirname);
 
2077
 
 
2078
    if (i == config->search_path_count) {
 
2079
        search_path_string = get_search_path_string(config);
 
2080
        if (reason) {
 
2081
            *reason = nvasprintf("the filename is not valid in the search path:\n\n%s\n",
 
2082
                                 search_path_string);
 
2083
        }
 
2084
        free(search_path_string);
 
2085
        return FALSE;
 
2086
    }
 
2087
 
 
2088
    // Check if the source file is a prefix of some other file in the configuration,
 
2089
    // or vice versa
 
2090
    for (i = 0, size = json_array_size(config->parsed_files); i < size; i++) {
 
2091
        parsed_file = json_array_get(config->parsed_files, i);
 
2092
        cur_filename = json_string_value(json_object_get(parsed_file, "filename"));
 
2093
 
 
2094
        prefix_state = app_profile_config_check_is_prefix(filename, cur_filename);
 
2095
 
 
2096
        if (prefix_state) {
 
2097
            if (prefix_state > 0) {
 
2098
                if (reason) {
 
2099
                    *reason = nvasprintf("the filename is a prefix of the existing file \"%s\".",
 
2100
                                         cur_filename);
 
2101
                }
 
2102
            } else if (reason) {
 
2103
                *reason = nvasprintf("the filename would be placed in the directory \"%s\", "
 
2104
                                     "but that is already a regular file in the configuration.",
 
2105
                                     cur_filename);
 
2106
            }
 
2107
            return FALSE;
 
2108
        }
 
2109
    }
 
2110
 
 
2111
    return TRUE;
 
2112
}
 
2113
 
 
2114
int nv_app_profile_config_check_backing_files(AppProfileConfig *config)
 
2115
{
 
2116
    json_t *file;
 
2117
    size_t i, size;
 
2118
    const char *filename;
 
2119
    FILE *fp;
 
2120
    time_t saved_atime;
 
2121
    struct stat stat_buf;
 
2122
    int ret;
 
2123
    int changed = FALSE;
 
2124
    for (i = 0, size = json_array_size(config->parsed_files); i < size; i++) {
 
2125
        file = json_array_get(config->parsed_files, i);
 
2126
        if (json_is_false(json_object_get(file, "new"))) {
 
2127
            // Stat the file and compare our saved atime to the file's mtime
 
2128
            filename = json_string_value(json_object_get(file, "filename"));
 
2129
            ret = open_and_stat(filename, "r", &fp, &stat_buf);
 
2130
            if (ret >= 0) {
 
2131
                fclose(fp);
 
2132
                saved_atime = (time_t)json_integer_value(json_object_get(file, "atime"));
 
2133
                if (stat_buf.st_mtime > saved_atime) {
 
2134
                    json_object_set_new(file, "dirty", json_true());
 
2135
                    changed = TRUE;
 
2136
                }
 
2137
            } else {
 
2138
                // I/O errors: assume something changed
 
2139
                json_object_set_new(file, "dirty", json_true());
 
2140
                changed = TRUE;
 
2141
            }
 
2142
        }
 
2143
    }
 
2144
 
 
2145
    return changed;
 
2146
}
 
2147
 
 
2148
/*
 
2149
 * Filenames in the search path ending in "*.d" are directories by convention,
 
2150
 * and should not be listed as valid default filenames.
 
2151
 */
 
2152
static inline int check_has_directory_suffix(const char *filename)
 
2153
{
 
2154
    size_t len = strlen(filename);
 
2155
 
 
2156
    return (len >= 2) &&
 
2157
           (filename[len-2] == '.') &&
 
2158
           (filename[len-1] == 'd');
 
2159
}
 
2160
 
 
2161
json_t *nv_app_profile_config_get_source_filenames(AppProfileConfig *config)
 
2162
{
 
2163
    size_t i, j, size;
 
2164
    const char *filename;
 
2165
    json_t *filenames;
 
2166
    json_t *file;
 
2167
    int do_add_search_path_item;
 
2168
 
 
2169
    filenames = json_array();
 
2170
    for (i = 0, size = json_array_size(config->parsed_files); i < size; i++) {
 
2171
        file = json_array_get(config->parsed_files, i);
 
2172
        json_array_append(filenames, json_object_get(file, "filename"));
 
2173
    }
 
2174
 
 
2175
    for (i = 0; i < config->search_path_count; i++) {
 
2176
        do_add_search_path_item =
 
2177
            !check_has_directory_suffix(config->search_path[i]);
 
2178
        for (j = 0; (j < size) && do_add_search_path_item; j++) {
 
2179
            file = json_array_get(config->parsed_files, j);
 
2180
            filename = json_string_value(json_object_get(file, "filename"));
 
2181
            if (!strcmp(config->search_path[i], filename) ||
 
2182
                app_profile_config_check_is_prefix(config->search_path[i],
 
2183
                                                   filename)) {
 
2184
                do_add_search_path_item = FALSE;
 
2185
            }
 
2186
        }
 
2187
        if (do_add_search_path_item) {
 
2188
            json_array_append_new(filenames,
 
2189
                                  json_string(config->search_path[i]));
 
2190
        }
 
2191
    }
 
2192
 
 
2193
    return filenames;
 
2194
}
 
2195
 
 
2196
int nv_app_profile_config_profile_name_change_fixup(AppProfileConfig *config,
 
2197
                                                     const char *orig_name,
 
2198
                                                     const char *new_name)
 
2199
{
 
2200
    int fixed_up = FALSE;
 
2201
    size_t i, j;
 
2202
    size_t num_files, num_rules;
 
2203
    json_t *file, *rules, *rule, *rule_profile;
 
2204
    const char *rule_profile_str;
 
2205
 
 
2206
    for (i = 0, num_files = json_array_size(config->parsed_files); i < num_files; i++) {
 
2207
        file = json_array_get(config->parsed_files, i);
 
2208
        rules = json_object_get(file, "rules");
 
2209
        for (j = 0, num_rules = json_array_size(rules); j < num_rules; j++) {
 
2210
            rule = json_array_get(rules, j);
 
2211
            rule_profile = json_object_get(rule, "profile");
 
2212
            assert(json_is_string(rule_profile));
 
2213
            rule_profile_str = json_string_value(rule_profile);
 
2214
            if (!strcmp(rule_profile_str, orig_name)) {
 
2215
                json_object_set_new(rule, "profile", json_string(new_name));
 
2216
                fixed_up = TRUE;
 
2217
            }
 
2218
        }
 
2219
    }
 
2220
 
 
2221
    return fixed_up;
 
2222
}