Ticket #3496: dir.c

File dir.c, 21.9 KB (added by Nicolas Rybkin, 9 years ago)

fixed dir.c

Line 
1/*
2   Directory routines
3
4   Copyright (C) 1994-2014
5   Free Software Foundation, Inc.
6
7   Written by:
8   Slava Zanko <slavazanko@gmail.com>, 2013
9   Andrew Borodin <aborodin@vmail.ru>, 2013
10
11   This file is part of the Midnight Commander.
12
13   The Midnight Commander is free software: you can redistribute it
14   and/or modify it under the terms of the GNU General Public License as
15   published by the Free Software Foundation, either version 3 of the License,
16   or (at your option) any later version.
17
18   The Midnight Commander is distributed in the hope that it will be useful,
19   but WITHOUT ANY WARRANTY; without even the implied warranty of
20   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21   GNU General Public License for more details.
22
23   You should have received a copy of the GNU General Public License
24   along with this program.  If not, see <http://www.gnu.org/licenses/>.
25 */
26
27/** \file src/filemanager/dir.c
28 *  \brief Source: directory routines
29 */
30
31#include <config.h>
32
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <sys/stat.h>
37
38#include "lib/global.h"
39#include "lib/tty/tty.h"
40#include "lib/search.h"
41#include "lib/vfs/vfs.h"
42#include "lib/fs.h"
43#include "lib/strutil.h"
44#include "lib/util.h"
45#include "lib/widget.h"         /* message() */
46
47#include "src/setup.h"          /* panels_options */
48
49#include "treestore.h"
50#include "dir.h"
51#include "layout.h"             /* rotate_dash() */
52
53/*** global variables ****************************************************************************/
54
55/*** file scope macro definitions ****************************************************************/
56
57#define MY_ISDIR(x) (\
58    (is_exe (x->st.st_mode) && !(S_ISDIR (x->st.st_mode) || x->f.link_to_dir) && exec_first) \
59        ? 1 \
60        : ( (S_ISDIR (x->st.st_mode) || x->f.link_to_dir) ? 2 : 0) )
61
62/*** file scope type declarations ****************************************************************/
63
64/*** file scope variables ************************************************************************/
65
66/* Reverse flag */
67static int reverse = 1;
68
69/* Are the files sorted case sensitively? */
70static int case_sensitive = OS_SORT_CASE_SENSITIVE_DEFAULT;
71
72/* Are the exec_bit files top in list */
73static gboolean exec_first = TRUE;
74
75static dir_list dir_copy = { NULL, 0, 0 };
76
77/*** file scope functions ************************************************************************/
78/* --------------------------------------------------------------------------------------------- */
79
80/*
81   sort_orders_t sort_orders [SORT_TYPES_TOTAL] = {
82   { N_("&Unsorted"),    unsorted },
83   { N_("&Name"),        sort_name },
84   { N_("&Extension"),   sort_ext },
85   { N_("&Modify time"), sort_time },
86   { N_("&Access time"), sort_atime },
87   { N_("C&Hange time"), sort_ctime },
88   { N_("&Size"),        sort_size },
89   { N_("&Inode"),       sort_inode },
90   };
91 */
92
93static inline int
94key_collate (const char *t1, const char *t2)
95{
96    int dotdot = 0;
97    int ret;
98
99    dotdot = (t1[0] == '.' ? 1 : 0) | ((t2[0] == '.' ? 1 : 0) << 1);
100
101    switch (dotdot)
102    {
103    case 0:
104    case 3:
105        ret = str_key_collate (t1, t2, case_sensitive) * reverse;
106        break;
107    case 1:
108        ret = -1;               /* t1 < t2 */
109        break;
110    case 2:
111        ret = 1;                /* t1 > t2 */
112        break;
113    default:
114        ret = 0;                /* it must not happen */
115    }
116
117    return ret;
118}
119
120/* --------------------------------------------------------------------------------------------- */
121/**
122 * clear keys, should be call after sorting is finished.
123 */
124
125static void
126clean_sort_keys (dir_list * list, int start, int count)
127{
128    int i;
129
130    for (i = 0; i < count; i++)
131    {
132        file_entry_t *fentry;
133
134        fentry = &list->list[i + start];
135        str_release_key (fentry->sort_key, case_sensitive);
136        fentry->sort_key = NULL;
137        str_release_key (fentry->second_sort_key, case_sensitive);
138        fentry->second_sort_key = NULL;
139    }
140}
141
142/* --------------------------------------------------------------------------------------------- */
143/**
144 * If you change handle_dirent then check also handle_path.
145 * @return FALSE = don't add, TRUE = add to the list
146 */
147
148static gboolean
149handle_dirent (struct dirent *dp, const char *fltr, struct stat *buf1, int *link_to_dir,
150               int *stale_link)
151{
152    vfs_path_t *vpath;
153
154    if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
155        return FALSE;
156    if (!panels_options.show_dot_files && (dp->d_name[0] == '.'))
157        return FALSE;
158    if (!panels_options.show_backups && dp->d_name[strlen (dp->d_name) - 1] == '~')
159        return FALSE;
160
161    vpath = vfs_path_from_str (dp->d_name);
162    if (mc_lstat (vpath, buf1) == -1)
163    {
164        /*
165         * lstat() fails - such entries should be identified by
166         * buf1->st_mode being 0.
167         * It happens on QNX Neutrino for /fs/cd0 if no CD is inserted.
168         */
169        memset (buf1, 0, sizeof (*buf1));
170    }
171
172    if (S_ISDIR (buf1->st_mode))
173        tree_store_mark_checked (dp->d_name);
174
175    /* A link to a file or a directory? */
176    *link_to_dir = 0;
177    *stale_link = 0;
178    if (S_ISLNK (buf1->st_mode))
179    {
180        struct stat buf2;
181
182        if (mc_stat (vpath, &buf2) == 0)
183            *link_to_dir = S_ISDIR (buf2.st_mode) != 0;
184        else
185            *stale_link = 1;
186    }
187
188    vfs_path_free (vpath);
189
190    return (S_ISDIR (buf1->st_mode) || *link_to_dir != 0 || fltr == NULL
191            || mc_search (fltr, NULL, dp->d_name, MC_SEARCH_T_GLOB));
192}
193
194/* --------------------------------------------------------------------------------------------- */
195/** get info about ".." */
196
197static gboolean
198dir_get_dotdot_stat (const vfs_path_t * vpath, struct stat *st)
199{
200    gboolean ret = FALSE;
201
202    if ((vpath != NULL) && (st != NULL))
203    {
204        const char *path;
205
206        path = vfs_path_get_by_index (vpath, 0)->path;
207        if (path != NULL && *path != '\0')
208        {
209            vfs_path_t *tmp_vpath;
210
211            tmp_vpath = vfs_path_append_new (vpath, "..", NULL);
212            ret = mc_stat (tmp_vpath, st) == 0;
213            vfs_path_free (tmp_vpath);
214        }
215    }
216
217    return ret;
218}
219
220/* --------------------------------------------------------------------------------------------- */
221
222static void
223alloc_dir_copy (int size)
224{
225    if (dir_copy.size < size)
226    {
227        if (dir_copy.list != NULL)
228        {
229            int i;
230
231            for (i = 0; i < dir_copy.len; i++)
232            {
233                file_entry_t *fentry;
234
235                fentry = &(dir_copy.list)[i];
236                g_free (fentry->fname);
237            }
238            g_free (dir_copy.list);
239        }
240
241        dir_copy.list = g_new0 (file_entry_t, size);
242        dir_copy.size = size;
243        dir_copy.len = 0;
244    }
245}
246
247/* --------------------------------------------------------------------------------------------- */
248/*** public functions ****************************************************************************/
249/* --------------------------------------------------------------------------------------------- */
250/**
251 * Increase or decrease directory list size.
252 *
253 * @param list directory list
254 * @param delta value by increase (if positive) or decrease (if negative) list size
255 *
256 * @return FALSE on failure, TRUE on success
257 */
258
259gboolean
260dir_list_grow (dir_list * list, int delta)
261{
262    int size;
263    gboolean clear = FALSE;
264
265    if (list == NULL)
266        return FALSE;
267
268    if (delta == 0)
269        return TRUE;
270
271    size = list->size + delta;
272    if (size <= 0)
273    {
274        size = DIR_LIST_MIN_SIZE;
275        clear = TRUE;
276    }
277
278    if (size != list->size)
279    {
280        file_entry_t *fe;
281
282        fe = g_try_renew (file_entry_t, list->list, size);
283        if (fe == NULL)
284            return FALSE;
285
286        list->list = fe;
287        list->size = size;
288    }
289
290    list->len = clear ? 0 : min (list->len, size);
291
292    return TRUE;
293}
294
295/* --------------------------------------------------------------------------------------------- */
296/**
297 * Append file info to the directory list.
298 *
299 * @param list directory list
300 * @param fname file name
301 * @param st file stat info
302 * @param link_to_dir is file link to directory
303 * @param stale_link is file stale elink
304 *
305 * @return FALSE on failure, TRUE on success
306 */
307
308gboolean
309dir_list_append (dir_list * list, const char *fname, const struct stat * st,
310                 gboolean link_to_dir, gboolean stale_link)
311{
312    file_entry_t *fentry;
313
314    /* Need to grow the *list? */
315    if (list->len == list->size && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
316        return FALSE;
317
318    fentry = &list->list[list->len];
319    fentry->fnamelen = strlen (fname);
320    fentry->fname = g_strndup (fname, fentry->fnamelen);
321    fentry->f.marked = 0;
322    fentry->f.link_to_dir = link_to_dir ? 1 : 0;
323    fentry->f.stale_link = stale_link ? 1 : 0;
324    fentry->f.dir_size_computed = 0;
325    fentry->st = *st;
326    fentry->sort_key = NULL;
327    fentry->second_sort_key = NULL;
328
329    list->len++;
330
331    return TRUE;
332}
333
334/* --------------------------------------------------------------------------------------------- */
335
336int
337unsorted (file_entry_t * a, file_entry_t * b)
338{
339    (void) a;
340    (void) b;
341    return 0;
342}
343
344/* --------------------------------------------------------------------------------------------- */
345
346int
347sort_name (file_entry_t * a, file_entry_t * b)
348{
349    int ad = MY_ISDIR (a);
350    int bd = MY_ISDIR (b);
351
352    if (ad == bd || panels_options.mix_all_files)
353    {
354        /* create key if does not exist, key will be freed after sorting */
355        if (a->sort_key == NULL)
356            a->sort_key = str_create_key_for_filename (a->fname, case_sensitive);
357        if (b->sort_key == NULL)
358            b->sort_key = str_create_key_for_filename (b->fname, case_sensitive);
359
360        return key_collate (a->sort_key, b->sort_key);
361    }
362    return bd - ad;
363}
364
365/* --------------------------------------------------------------------------------------------- */
366
367int
368sort_vers (file_entry_t * a, file_entry_t * b)
369{
370    int ad = MY_ISDIR (a);
371    int bd = MY_ISDIR (b);
372
373    if (ad == bd || panels_options.mix_all_files)
374    {
375        return str_verscmp (a->fname, b->fname) * reverse;
376    }
377    else
378    {
379        return bd - ad;
380    }
381}
382
383/* --------------------------------------------------------------------------------------------- */
384
385int
386sort_ext (file_entry_t * a, file_entry_t * b)
387{
388    int ad = MY_ISDIR (a);
389    int bd = MY_ISDIR (b);
390
391    if (ad == bd || panels_options.mix_all_files)
392    {
393        int r;
394
395        if (a->second_sort_key == NULL)
396            a->second_sort_key = str_create_key (extension (a->fname), case_sensitive);
397        if (b->second_sort_key == NULL)
398            b->second_sort_key = str_create_key (extension (b->fname), case_sensitive);
399
400        r = str_key_collate (a->second_sort_key, b->second_sort_key, case_sensitive);
401        if (r)
402            return r * reverse;
403        else
404            return sort_name (a, b);
405    }
406    else
407        return bd - ad;
408}
409
410/* --------------------------------------------------------------------------------------------- */
411
412int
413sort_time (file_entry_t * a, file_entry_t * b)
414{
415    int ad = MY_ISDIR (a);
416    int bd = MY_ISDIR (b);
417
418    if (ad == bd || panels_options.mix_all_files)
419    {
420        int result = a->st.st_mtime < b->st.st_mtime ? -1 : a->st.st_mtime > b->st.st_mtime;
421        if (result != 0)
422            return result * reverse;
423        else
424            return sort_name (a, b);
425    }
426    else
427        return bd - ad;
428}
429
430/* --------------------------------------------------------------------------------------------- */
431
432int
433sort_ctime (file_entry_t * a, file_entry_t * b)
434{
435    int ad = MY_ISDIR (a);
436    int bd = MY_ISDIR (b);
437
438    if (ad == bd || panels_options.mix_all_files)
439    {
440        int result = a->st.st_ctime < b->st.st_ctime ? -1 : a->st.st_ctime > b->st.st_ctime;
441        if (result != 0)
442            return result * reverse;
443        else
444            return sort_name (a, b);
445    }
446    else
447        return bd - ad;
448}
449
450/* --------------------------------------------------------------------------------------------- */
451
452int
453sort_atime (file_entry_t * a, file_entry_t * b)
454{
455    int ad = MY_ISDIR (a);
456    int bd = MY_ISDIR (b);
457
458    if (ad == bd || panels_options.mix_all_files)
459    {
460        int result = a->st.st_atime < b->st.st_atime ? -1 : a->st.st_atime > b->st.st_atime;
461        if (result != 0)
462            return result * reverse;
463        else
464            return sort_name (a, b);
465    }
466    else
467        return bd - ad;
468}
469
470/* --------------------------------------------------------------------------------------------- */
471
472int
473sort_inode (file_entry_t * a, file_entry_t * b)
474{
475    int ad = MY_ISDIR (a);
476    int bd = MY_ISDIR (b);
477
478    if (ad == bd || panels_options.mix_all_files)
479        return (a->st.st_ino - b->st.st_ino) * reverse;
480    else
481        return bd - ad;
482}
483
484/* --------------------------------------------------------------------------------------------- */
485
486int
487sort_size (file_entry_t * a, file_entry_t * b)
488{
489    int ad = MY_ISDIR (a);
490    int bd = MY_ISDIR (b);
491    int result = 0;
492
493    if (ad != bd && !panels_options.mix_all_files)
494        return bd - ad;
495
496    result = a->st.st_size < b->st.st_size ? -1 : a->st.st_size > b->st.st_size;
497    if (result != 0)
498        return result * reverse;
499    else
500        return sort_name (a, b);
501}
502
503/* --------------------------------------------------------------------------------------------- */
504
505void
506dir_list_sort (dir_list * list, GCompareFunc sort, const dir_sort_options_t * sort_op)
507{
508    file_entry_t *fentry;
509    int dot_dot_found = 0;
510
511    if (list->len < 2)
512        return;
513
514    /* If there is an ".." entry the caller must take care to
515       ensure that it occupies the first list element. */
516    fentry = &list->list[0];
517    if (DIR_IS_DOTDOT (fentry->fname))
518        dot_dot_found = 1;
519
520    reverse = sort_op->reverse ? -1 : 1;
521    case_sensitive = sort_op->case_sensitive ? 1 : 0;
522    exec_first = sort_op->exec_first;
523    if (sort != (GCompareFunc)unsorted)
524    {
525        qsort (&(list->list)[dot_dot_found], list->len - dot_dot_found, sizeof (file_entry_t), sort);
526    }
527
528    clean_sort_keys (list, dot_dot_found, list->len - dot_dot_found);
529}
530
531/* --------------------------------------------------------------------------------------------- */
532
533void
534dir_list_clean (dir_list * list)
535{
536    int i;
537
538    for (i = 0; i < list->len; i++)
539    {
540        file_entry_t *fentry;
541
542        fentry = &list->list[i];
543        MC_PTR_FREE (fentry->fname);
544    }
545
546    list->len = 0;
547    /* reduce memory usage */
548    dir_list_grow (list, DIR_LIST_MIN_SIZE - list->size);
549}
550
551/* --------------------------------------------------------------------------------------------- */
552/** Used to set up a directory list when there is no access to a directory */
553
554gboolean
555dir_list_init (dir_list * list)
556{
557    file_entry_t *fentry;
558
559    /* Need to grow the *list? */
560    if (list->size == 0 && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
561    {
562        list->len = 0;
563        return FALSE;
564    }
565
566    fentry = &list->list[0];
567    memset (fentry, 0, sizeof (file_entry_t));
568    fentry->fnamelen = 2;
569    fentry->fname = g_strndup ("..", fentry->fnamelen);
570    fentry->f.link_to_dir = 0;
571    fentry->f.stale_link = 0;
572    fentry->f.dir_size_computed = 0;
573    fentry->f.marked = 0;
574    fentry->st.st_mode = 040755;
575    list->len = 1;
576    return TRUE;
577}
578
579/* --------------------------------------------------------------------------------------------- */
580/**
581   handle_path is a simplified handle_dirent. The difference is that
582   handle_path doesn't pay attention to panels_options.show_dot_files
583   and panels_options.show_backups.
584   Moreover handle_path can't be used with a filemask.
585   If you change handle_path then check also handle_dirent. */
586/* Return values: FALSE = don't add, TRUE = add to the list */
587
588gboolean
589handle_path (const char *path, struct stat * buf1, int *link_to_dir, int *stale_link)
590{
591    vfs_path_t *vpath;
592
593    if (DIR_IS_DOT (path) || DIR_IS_DOTDOT (path))
594        return FALSE;
595
596    vpath = vfs_path_from_str (path);
597    if (mc_lstat (vpath, buf1) == -1)
598    {
599        vfs_path_free (vpath);
600        return FALSE;
601    }
602
603    if (S_ISDIR (buf1->st_mode))
604        tree_store_mark_checked (path);
605
606    /* A link to a file or a directory? */
607    *link_to_dir = 0;
608    *stale_link = 0;
609    if (S_ISLNK (buf1->st_mode))
610    {
611        struct stat buf2;
612
613        if (mc_stat (vpath, &buf2) == 0)
614            *link_to_dir = S_ISDIR (buf2.st_mode) != 0;
615        else
616            *stale_link = 1;
617    }
618
619    vfs_path_free (vpath);
620
621    return TRUE;
622}
623
624/* --------------------------------------------------------------------------------------------- */
625
626void
627dir_list_load (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
628               const dir_sort_options_t * sort_op, const char *fltr)
629{
630    DIR *dirp;
631    struct dirent *dp;
632    int link_to_dir, stale_link;
633    struct stat st;
634    file_entry_t *fentry;
635
636    /* ".." (if any) must be the first entry in the list */
637    if (!dir_list_init (list))
638        return;
639
640    fentry = &list->list[0];
641    if (dir_get_dotdot_stat (vpath, &st))
642        fentry->st = st;
643
644    dirp = mc_opendir (vpath);
645    if (dirp == NULL)
646    {
647        message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
648        return;
649    }
650
651    tree_store_start_check (vpath);
652
653    {
654        const char *vpath_str;
655
656        vpath_str = vfs_path_as_str (vpath);
657        /* Do not add a ".." entry to the root directory */
658        if ((vpath_str[0] == PATH_SEP) && (vpath_str[1] == '\0'))
659            list->len--;
660    }
661
662    while ((dp = mc_readdir (dirp)) != NULL)
663    {
664        if (!handle_dirent (dp, fltr, &st, &link_to_dir, &stale_link))
665            continue;
666
667        if (!dir_list_append (list, dp->d_name, &st, link_to_dir != 0, stale_link != 0))
668            goto ret;
669
670        if ((list->len & 31) == 0)
671            rotate_dash (TRUE);
672    }
673
674    dir_list_sort (list, sort, sort_op);
675
676  ret:
677    mc_closedir (dirp);
678    tree_store_end_check ();
679    rotate_dash (FALSE);
680}
681
682/* --------------------------------------------------------------------------------------------- */
683
684gboolean
685if_link_is_exe (const vfs_path_t * full_name_vpath, const file_entry_t * file)
686{
687    struct stat b;
688
689    if (S_ISLNK (file->st.st_mode) && mc_stat (full_name_vpath, &b) == 0)
690        return is_exe (b.st_mode);
691    return TRUE;
692}
693
694/* --------------------------------------------------------------------------------------------- */
695/** If fltr is null, then it is a match */
696
697void
698dir_list_reload (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
699                 const dir_sort_options_t * sort_op, const char *fltr)
700{
701    DIR *dirp;
702    struct dirent *dp;
703    int i, link_to_dir, stale_link;
704    struct stat st;
705    int marked_cnt;
706    GHashTable *marked_files;
707    const char *tmp_path;
708
709    dirp = mc_opendir (vpath);
710    if (dirp == NULL)
711    {
712        message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
713        dir_list_clean (list);
714        dir_list_init (list);
715        return;
716    }
717
718    tree_store_start_check (vpath);
719
720    marked_files = g_hash_table_new (g_str_hash, g_str_equal);
721    alloc_dir_copy (list->len);
722    for (marked_cnt = i = 0; i < list->len; i++)
723    {
724        file_entry_t *fentry, *dfentry;
725
726        fentry = &list->list[i];
727        dfentry = &dir_copy.list[i];
728
729        dfentry->fnamelen = fentry->fnamelen;
730        dfentry->fname = fentry->fname;
731        dfentry->f.marked = fentry->f.marked;
732        dfentry->f.dir_size_computed = fentry->f.dir_size_computed;
733        dfentry->f.link_to_dir = fentry->f.link_to_dir;
734        dfentry->f.stale_link = fentry->f.stale_link;
735        dfentry->sort_key = NULL;
736        dfentry->second_sort_key = NULL;
737        if (fentry->f.marked)
738        {
739            g_hash_table_insert (marked_files, dfentry->fname, dfentry);
740            marked_cnt++;
741        }
742    }
743
744    /* Add ".." except to the root directory. The ".." entry
745       (if any) must be the first in the list. */
746    tmp_path = vfs_path_get_by_index (vpath, 0)->path;
747    if (vfs_path_elements_count (vpath) == 1 && tmp_path[0] == PATH_SEP && tmp_path[1] == '\0')
748    {
749        /* root directory */
750        dir_list_clean (list);
751    }
752    else
753    {
754        if (!dir_list_init (list))
755        {
756            dir_list_clean (&dir_copy);
757            return;
758        }
759
760        if (dir_get_dotdot_stat (vpath, &st))
761        {
762            file_entry_t *fentry;
763
764            fentry = &list->list[0];
765            fentry->st = st;
766        }
767    }
768
769    while ((dp = mc_readdir (dirp)) != NULL)
770    {
771        file_entry_t *fentry;
772
773        if (!handle_dirent (dp, fltr, &st, &link_to_dir, &stale_link))
774            continue;
775
776        if (!dir_list_append (list, dp->d_name, &st, link_to_dir != 0, stale_link != 0))
777        {
778            mc_closedir (dirp);
779            /* Norbert (Feb 12, 1997):
780               Just in case someone finds this memory leak:
781               -1 means big trouble (at the moment no memory left),
782               I don't bother with further cleanup because if one gets to
783               this point he will have more problems than a few memory
784               leaks and because one 'dir_list_clean' would not be enough (and
785               because I don't want to spent the time to make it working,
786               IMHO it's not worthwhile).
787               dir_list_clean (&dir_copy);
788             */
789            tree_store_end_check ();
790            g_hash_table_destroy (marked_files);
791            return;
792        }
793        fentry = &list->list[list->len - 1];
794
795        fentry->f.marked = 0;
796
797        /*
798         * If we have marked files in the copy, scan through the copy
799         * to find matching file.  Decrease number of remaining marks if
800         * we copied one.
801         */
802        if (marked_cnt > 0 && g_hash_table_lookup (marked_files, dp->d_name) != NULL)
803        {
804            fentry->f.marked = 1;
805            marked_cnt--;
806        }
807
808        if ((list->len & 15) == 0)
809            rotate_dash (TRUE);
810    }
811    mc_closedir (dirp);
812    tree_store_end_check ();
813    g_hash_table_destroy (marked_files);
814
815    dir_list_sort (list, sort, sort_op);
816
817    dir_list_clean (&dir_copy);
818    rotate_dash (FALSE);
819}
820
821/* --------------------------------------------------------------------------------------------- */