Manual pages: mcmcdiffmceditmcview

root/src/filemanager/chmod.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. chmod_init
  2. chmod_draw_select
  3. chmod_toggle_select
  4. chmod_refresh
  5. chmod_bg_callback
  6. chmod_callback
  7. chmod_dlg_create
  8. chmod_done
  9. try_chmod
  10. do_chmod
  11. apply_mask
  12. chmod_cmd

   1 /*
   2    Chmod command -- for the Midnight Commander
   3 
   4    Copyright (C) 1994-2025
   5    Free Software Foundation, Inc.
   6 
   7    This file is part of the Midnight Commander.
   8 
   9    The Midnight Commander is free software: you can redistribute it
  10    and/or modify it under the terms of the GNU General Public License as
  11    published by the Free Software Foundation, either version 3 of the License,
  12    or (at your option) any later version.
  13 
  14    The Midnight Commander is distributed in the hope that it will be useful,
  15    but WITHOUT ANY WARRANTY; without even the implied warranty of
  16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17    GNU General Public License for more details.
  18 
  19    You should have received a copy of the GNU General Public License
  20    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  21  */
  22 
  23 /** \file chmod.c
  24  *  \brief Source: chmod command
  25  */
  26 
  27 #include <config.h>
  28 
  29 #include <errno.h>
  30 #include <sys/types.h>
  31 #include <sys/stat.h>
  32 #include <unistd.h>
  33 
  34 #include "lib/global.h"
  35 
  36 #include "lib/tty/tty.h"
  37 #include "lib/skin.h"
  38 #include "lib/vfs/vfs.h"
  39 #include "lib/strutil.h"
  40 #include "lib/util.h"
  41 #include "lib/widget.h"
  42 
  43 #include "src/util.h"  // file_error_message()
  44 
  45 #include "cmd.h"  // chmod_cmd()
  46 
  47 /*** global variables ****************************************************************************/
  48 
  49 /*** file scope macro definitions ****************************************************************/
  50 
  51 #define PX           3
  52 #define PY           2
  53 
  54 #define B_MARKED     B_USER
  55 #define B_SETALL     (B_USER + 1)
  56 #define B_SETMRK     (B_USER + 2)
  57 #define B_CLRMRK     (B_USER + 3)
  58 
  59 #define BUTTONS      6
  60 #define BUTTONS_PERM 12
  61 #define LABELS       4
  62 
  63 /*** file scope type declarations ****************************************************************/
  64 
  65 /*** forward declarations (file scope functions) *************************************************/
  66 
  67 /*** file scope variables ************************************************************************/
  68 
  69 static struct
  70 {
  71     mode_t mode;
  72     const char *text;
  73     gboolean selected;
  74     WCheck *check;
  75 } check_perm[BUTTONS_PERM] = {
  76     { S_ISUID, N_ ("set &user ID on execution"), FALSE, NULL },
  77     { S_ISGID, N_ ("set &group ID on execution"), FALSE, NULL },
  78     { S_ISVTX, N_ ("stick&y bit"), FALSE, NULL },
  79     { S_IRUSR, N_ ("&read by owner"), FALSE, NULL },
  80     { S_IWUSR, N_ ("&write by owner"), FALSE, NULL },
  81     { S_IXUSR, N_ ("e&xecute/search by owner"), FALSE, NULL },
  82     { S_IRGRP, N_ ("rea&d by group"), FALSE, NULL },
  83     { S_IWGRP, N_ ("write by grou&p"), FALSE, NULL },
  84     { S_IXGRP, N_ ("execu&te/search by group"), FALSE, NULL },
  85     { S_IROTH, N_ ("read &by others"), FALSE, NULL },
  86     { S_IWOTH, N_ ("wr&ite by others"), FALSE, NULL },
  87     { S_IXOTH, N_ ("execute/searc&h by others"), FALSE, NULL },
  88 };
  89 
  90 static int check_perm_len = 0;
  91 
  92 static const char *file_info_labels[LABELS] = {
  93     N_ ("Name:"),
  94     N_ ("Permissions (octal):"),
  95     N_ ("Owner name:"),
  96     N_ ("Group name:"),
  97 };
  98 
  99 static int file_info_labels_len = 0;
 100 
 101 static struct
 102 {
 103     int ret_cmd;
 104     button_flags_t flags;
 105     int y;  // vertical position relatively to dialog bottom boundary
 106     const char *text;
 107 } chmod_but[BUTTONS] = {
 108     { B_SETALL, NORMAL_BUTTON, 6, N_ ("Set &all") },
 109     { B_MARKED, NORMAL_BUTTON, 6, N_ ("&Marked all") },
 110     { B_SETMRK, NORMAL_BUTTON, 5, N_ ("S&et marked") },
 111     { B_CLRMRK, NORMAL_BUTTON, 5, N_ ("C&lear marked") },
 112     { B_ENTER, DEFPUSH_BUTTON, 3, N_ ("&Set") },
 113     { B_CANCEL, NORMAL_BUTTON, 3, N_ ("&Cancel") },
 114 };
 115 
 116 static gboolean mode_change;
 117 static int current_file;
 118 static gboolean ignore_all;
 119 
 120 static mode_t and_mask, or_mask, ch_mode;
 121 
 122 static WLabel *statl;
 123 static WGroupbox *file_gb;
 124 
 125 /* --------------------------------------------------------------------------------------------- */
 126 /*** file scope functions ************************************************************************/
 127 /* --------------------------------------------------------------------------------------------- */
 128 
 129 static void
 130 chmod_init (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 131 {
 132     static gboolean i18n = FALSE;
 133     int i, len;
 134 
 135     for (i = 0; i < BUTTONS_PERM; i++)
 136         check_perm[i].selected = FALSE;
 137 
 138     if (i18n)
 139         return;
 140 
 141     i18n = TRUE;
 142 
 143 #ifdef ENABLE_NLS
 144     for (i = 0; i < BUTTONS_PERM; i++)
 145         check_perm[i].text = _ (check_perm[i].text);
 146 
 147     for (i = 0; i < LABELS; i++)
 148         file_info_labels[i] = _ (file_info_labels[i]);
 149 
 150     for (i = 0; i < BUTTONS; i++)
 151         chmod_but[i].text = _ (chmod_but[i].text);
 152 #endif
 153 
 154     for (i = 0; i < BUTTONS_PERM; i++)
 155     {
 156         len = str_term_width1 (check_perm[i].text);
 157         check_perm_len = MAX (check_perm_len, len);
 158     }
 159 
 160     check_perm_len += 1 + 3 + 1;  // mark, [x] and space
 161 
 162     for (i = 0; i < LABELS; i++)
 163     {
 164         len = str_term_width1 (file_info_labels[i]) + 2;  // spaces around
 165         file_info_labels_len = MAX (file_info_labels_len, len);
 166     }
 167 }
 168 
 169 /* --------------------------------------------------------------------------------------------- */
 170 
 171 static void
 172 chmod_draw_select (const WDialog *h, int Id)
     /* [previous][next][first][last][top][bottom][index][help]  */
 173 {
 174     widget_gotoyx (h, PY + Id + 1, PX + 1);
 175     tty_print_char (check_perm[Id].selected ? '*' : ' ');
 176     widget_gotoyx (h, PY + Id + 1, PX + 3);
 177 }
 178 
 179 /* --------------------------------------------------------------------------------------------- */
 180 
 181 static void
 182 chmod_toggle_select (const WDialog *h, int Id)
     /* [previous][next][first][last][top][bottom][index][help]  */
 183 {
 184     check_perm[Id].selected = !check_perm[Id].selected;
 185     tty_setcolor (COLOR_NORMAL);
 186     chmod_draw_select (h, Id);
 187 }
 188 
 189 /* --------------------------------------------------------------------------------------------- */
 190 
 191 static void
 192 chmod_refresh (const WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 193 {
 194     int i;
 195     int y, x;
 196 
 197     tty_setcolor (COLOR_NORMAL);
 198 
 199     for (i = 0; i < BUTTONS_PERM; i++)
 200         chmod_draw_select (h, i);
 201 
 202     y = WIDGET (file_gb)->rect.y + 1;
 203     x = WIDGET (file_gb)->rect.x + 2;
 204 
 205     tty_gotoyx (y, x);
 206     tty_print_string (file_info_labels[0]);
 207     tty_gotoyx (y + 2, x);
 208     tty_print_string (file_info_labels[1]);
 209     tty_gotoyx (y + 4, x);
 210     tty_print_string (file_info_labels[2]);
 211     tty_gotoyx (y + 6, x);
 212     tty_print_string (file_info_labels[3]);
 213 }
 214 
 215 /* --------------------------------------------------------------------------------------------- */
 216 
 217 static cb_ret_t
 218 chmod_bg_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 219 {
 220     switch (msg)
 221     {
 222     case MSG_DRAW:
 223         frame_callback (w, NULL, MSG_DRAW, 0, NULL);
 224         chmod_refresh (CONST_DIALOG (w->owner));
 225         return MSG_HANDLED;
 226 
 227     default:
 228         return frame_callback (w, sender, msg, parm, data);
 229     }
 230 }
 231 
 232 /* --------------------------------------------------------------------------------------------- */
 233 
 234 static cb_ret_t
 235 chmod_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 236 {
 237     WGroup *g = GROUP (w);
 238     WDialog *h = DIALOG (w);
 239 
 240     switch (msg)
 241     {
 242     case MSG_NOTIFY:
 243     {
 244         // handle checkboxes
 245         int i;
 246 
 247         // whether notification was sent by checkbox?
 248         for (i = 0; i < BUTTONS_PERM; i++)
 249             if (sender == WIDGET (check_perm[i].check))
 250                 break;
 251 
 252         if (i < BUTTONS_PERM)
 253         {
 254             ch_mode ^= check_perm[i].mode;
 255             label_set_textv (statl, "%o", (unsigned int) ch_mode);
 256             chmod_toggle_select (h, i);
 257             mode_change = TRUE;
 258             return MSG_HANDLED;
 259         }
 260     }
 261 
 262         return MSG_NOT_HANDLED;
 263 
 264     case MSG_KEY:
 265         if (parm == 'T' || parm == 't' || parm == KEY_IC)
 266         {
 267             int i;
 268             unsigned long id;
 269 
 270             id = group_get_current_widget_id (g);
 271             for (i = 0; i < BUTTONS_PERM; i++)
 272                 if (id == WIDGET (check_perm[i].check)->id)
 273                     break;
 274 
 275             if (i < BUTTONS_PERM)
 276             {
 277                 chmod_toggle_select (h, i);
 278                 if (parm == KEY_IC)
 279                     group_select_next_widget (g);
 280                 return MSG_HANDLED;
 281             }
 282         }
 283         return MSG_NOT_HANDLED;
 284 
 285     default:
 286         return dlg_default_callback (w, sender, msg, parm, data);
 287     }
 288 }
 289 
 290 /* --------------------------------------------------------------------------------------------- */
 291 
 292 static WDialog *
 293 chmod_dlg_create (WPanel *panel, const char *fname, const struct stat *sf_stat)
     /* [previous][next][first][last][top][bottom][index][help]  */
 294 {
 295     gboolean single_set;
 296     WDialog *ch_dlg;
 297     WGroup *g;
 298     int lines, cols;
 299     int i, y;
 300     int perm_gb_len;
 301     int file_gb_len;
 302     const char *c_fname, *c_fown, *c_fgrp;
 303     char buffer[BUF_TINY];
 304 
 305     mode_change = FALSE;
 306 
 307     single_set = (panel->marked < 2);
 308     perm_gb_len = check_perm_len + 2;
 309     file_gb_len = file_info_labels_len + 2;
 310     cols = str_term_width1 (fname) + 2 + 1;
 311     file_gb_len = MAX (file_gb_len, cols);
 312 
 313     lines = single_set ? 20 : 23;
 314     cols = perm_gb_len + file_gb_len + 1 + 6;
 315 
 316     if (cols > COLS)
 317     {
 318         // shrink the right groupbox
 319         cols = COLS;
 320         file_gb_len = cols - (perm_gb_len + 1 + 6);
 321     }
 322 
 323     ch_dlg = dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, chmod_callback,
 324                          NULL, "[Chmod]", _ ("Chmod command"));
 325     g = GROUP (ch_dlg);
 326 
 327     // draw background
 328     ch_dlg->bg->callback = chmod_bg_callback;
 329 
 330     group_add_widget (g, groupbox_new (PY, PX, BUTTONS_PERM + 2, perm_gb_len, _ ("Permission")));
 331 
 332     for (i = 0; i < BUTTONS_PERM; i++)
 333     {
 334         check_perm[i].check =
 335             check_new (PY + i + 1, PX + 2, (ch_mode & check_perm[i].mode) != 0, check_perm[i].text);
 336         group_add_widget (g, check_perm[i].check);
 337     }
 338 
 339     file_gb = groupbox_new (PY, PX + perm_gb_len + 1, BUTTONS_PERM + 2, file_gb_len, _ ("File"));
 340     group_add_widget (g, file_gb);
 341 
 342     // Set the labels
 343     y = PY + 2;
 344     cols = PX + perm_gb_len + 3;
 345     c_fname = str_trunc (fname, file_gb_len - 3);
 346     group_add_widget (g, label_new (y, cols, c_fname));
 347     g_snprintf (buffer, sizeof (buffer), "%o", (unsigned int) ch_mode);
 348     statl = label_new (y + 2, cols, buffer);
 349     group_add_widget (g, statl);
 350     c_fown = str_trunc (get_owner (sf_stat->st_uid), file_gb_len - 3);
 351     group_add_widget (g, label_new (y + 4, cols, c_fown));
 352     c_fgrp = str_trunc (get_group (sf_stat->st_gid), file_gb_len - 3);
 353     group_add_widget (g, label_new (y + 6, cols, c_fgrp));
 354 
 355     for (i = single_set ? BUTTONS - 2 : 0; i < BUTTONS; i++)
 356     {
 357         WButton *b;
 358 
 359         y = lines - chmod_but[i].y;
 360 
 361         if (i == 0 || i == BUTTONS - 2)
 362             group_add_widget (g, hline_new (y - 1, -1, -1));
 363 
 364         b = button_new (y, 1, chmod_but[i].ret_cmd, chmod_but[i].flags, chmod_but[i].text, NULL);
 365         WIDGET (b)->rect.x = WIDGET (ch_dlg)->rect.cols / 2 - button_get_width (b);
 366         group_add_widget (g, b);
 367         i++;
 368         b = button_new (y, 1, chmod_but[i].ret_cmd, chmod_but[i].flags, chmod_but[i].text, NULL);
 369         WIDGET (b)->rect.x = WIDGET (ch_dlg)->rect.cols / 2 + 1;
 370         group_add_widget (g, b);
 371     }
 372 
 373     // select first checkbox
 374     widget_select (WIDGET (check_perm[0].check));
 375 
 376     return ch_dlg;
 377 }
 378 
 379 /* --------------------------------------------------------------------------------------------- */
 380 
 381 static void
 382 chmod_done (gboolean need_update)
     /* [previous][next][first][last][top][bottom][index][help]  */
 383 {
 384     if (need_update)
 385         update_panels (UP_OPTIMIZE, UP_KEEPSEL);
 386     repaint_screen ();
 387 }
 388 
 389 /* --------------------------------------------------------------------------------------------- */
 390 
 391 static gboolean
 392 try_chmod (const vfs_path_t *p, mode_t m)
     /* [previous][next][first][last][top][bottom][index][help]  */
 393 {
 394     const char *fname = NULL;
 395 
 396     while (mc_chmod (p, m) == -1 && !ignore_all)
 397     {
 398         int my_errno = errno;
 399 
 400         if (fname == NULL)
 401             fname = x_basename (vfs_path_as_str (p));
 402 
 403         errno = my_errno;  // restore errno for file_error(
 404 
 405         switch (file_error (NULL, TRUE, _ ("Cannot chmod\n%sn%s"), fname))
 406         {
 407         case FILE_IGNORE:
 408             // try next file
 409             return TRUE;
 410 
 411         case FILE_IGNORE_ALL:
 412             ignore_all = TRUE;
 413             // try next file
 414             return TRUE;
 415 
 416         case FILE_RETRY:
 417             // retry this file
 418             break;
 419 
 420         case FILE_ABORT:
 421         default:
 422             // stop remain files processing
 423             return FALSE;
 424         }
 425     }
 426 
 427     return TRUE;
 428 }
 429 
 430 /* --------------------------------------------------------------------------------------------- */
 431 
 432 static gboolean
 433 do_chmod (WPanel *panel, const vfs_path_t *p, struct stat *sf)
     /* [previous][next][first][last][top][bottom][index][help]  */
 434 {
 435     gboolean ret;
 436 
 437     sf->st_mode &= and_mask;
 438     sf->st_mode |= or_mask;
 439 
 440     ret = try_chmod (p, sf->st_mode);
 441 
 442     do_file_mark (panel, current_file, 0);
 443 
 444     return ret;
 445 }
 446 
 447 /* --------------------------------------------------------------------------------------------- */
 448 
 449 static void
 450 apply_mask (WPanel *panel, vfs_path_t *vpath, struct stat *sf)
     /* [previous][next][first][last][top][bottom][index][help]  */
 451 {
 452     gboolean ok;
 453 
 454     if (!do_chmod (panel, vpath, sf))
 455         return;
 456 
 457     do
 458     {
 459         const GString *fname;
 460 
 461         fname = panel_find_marked_file (panel, &current_file);
 462         vpath = vfs_path_from_str (fname->str);
 463         ok = (mc_stat (vpath, sf) == 0);
 464 
 465         if (!ok)
 466         {
 467             // if current file was deleted outside mc -- try next file
 468             // decrease panel->marked
 469             do_file_mark (panel, current_file, 0);
 470 
 471             // try next file
 472             ok = TRUE;
 473         }
 474         else
 475         {
 476             ch_mode = sf->st_mode;
 477 
 478             ok = do_chmod (panel, vpath, sf);
 479         }
 480 
 481         vfs_path_free (vpath, TRUE);
 482     }
 483     while (ok && panel->marked != 0);
 484 }
 485 
 486 /* --------------------------------------------------------------------------------------------- */
 487 /*** public functions ****************************************************************************/
 488 /* --------------------------------------------------------------------------------------------- */
 489 
 490 void
 491 chmod_cmd (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
 492 {
 493     gboolean need_update;
 494     gboolean end_chmod;
 495 
 496     chmod_init ();
 497 
 498     current_file = 0;
 499     ignore_all = FALSE;
 500 
 501     do
 502     {  // do while any files remaining
 503         vfs_path_t *vpath;
 504         WDialog *ch_dlg;
 505         struct stat sf_stat;
 506         const GString *fname;
 507         int i, result;
 508 
 509         do_refresh ();
 510 
 511         need_update = FALSE;
 512         end_chmod = FALSE;
 513 
 514         fname = panel_get_marked_file (panel, &current_file);
 515         if (fname == NULL)
 516             break;
 517 
 518         vpath = vfs_path_from_str (fname->str);
 519 
 520         if (mc_stat (vpath, &sf_stat) != 0)
 521         {
 522             vfs_path_free (vpath, TRUE);
 523             break;
 524         }
 525 
 526         ch_mode = sf_stat.st_mode;
 527 
 528         ch_dlg = chmod_dlg_create (panel, fname->str, &sf_stat);
 529         result = dlg_run (ch_dlg);
 530 
 531         switch (result)
 532         {
 533         case B_CANCEL:
 534             end_chmod = TRUE;
 535             break;
 536 
 537         case B_ENTER:
 538             if (mode_change)
 539             {
 540                 if (panel->marked <= 1)
 541                 {
 542                     // single or last file
 543                     if (mc_chmod (vpath, ch_mode) == -1 && !ignore_all)
 544                         file_error_message (_ ("Cannot chmod\n%s"), fname->str);
 545                     end_chmod = TRUE;
 546                 }
 547                 else if (!try_chmod (vpath, ch_mode))
 548                 {
 549                     // stop multiple files processing
 550                     result = B_CANCEL;
 551                     end_chmod = TRUE;
 552                 }
 553             }
 554 
 555             need_update = TRUE;
 556             break;
 557 
 558         case B_SETALL:
 559         case B_MARKED:
 560             and_mask = or_mask = 0;
 561             and_mask = ~and_mask;
 562 
 563             for (i = 0; i < BUTTONS_PERM; i++)
 564                 if (check_perm[i].selected || result == B_SETALL)
 565                 {
 566                     if (check_perm[i].check->state)
 567                         or_mask |= check_perm[i].mode;
 568                     else
 569                         and_mask &= ~check_perm[i].mode;
 570                 }
 571 
 572             apply_mask (panel, vpath, &sf_stat);
 573             need_update = TRUE;
 574             end_chmod = TRUE;
 575             break;
 576 
 577         case B_SETMRK:
 578             and_mask = or_mask = 0;
 579             and_mask = ~and_mask;
 580 
 581             for (i = 0; i < BUTTONS_PERM; i++)
 582                 if (check_perm[i].selected)
 583                     or_mask |= check_perm[i].mode;
 584 
 585             apply_mask (panel, vpath, &sf_stat);
 586             need_update = TRUE;
 587             end_chmod = TRUE;
 588             break;
 589 
 590         case B_CLRMRK:
 591             and_mask = or_mask = 0;
 592             and_mask = ~and_mask;
 593 
 594             for (i = 0; i < BUTTONS_PERM; i++)
 595                 if (check_perm[i].selected)
 596                     and_mask &= ~check_perm[i].mode;
 597 
 598             apply_mask (panel, vpath, &sf_stat);
 599             need_update = TRUE;
 600             end_chmod = TRUE;
 601             break;
 602 
 603         default:
 604             break;
 605         }
 606 
 607         if (panel->marked != 0 && result != B_CANCEL)
 608         {
 609             do_file_mark (panel, current_file, 0);
 610             need_update = TRUE;
 611         }
 612 
 613         vfs_path_free (vpath, TRUE);
 614 
 615         widget_destroy (WIDGET (ch_dlg));
 616     }
 617     while (panel->marked != 0 && !end_chmod);
 618 
 619     chmod_done (need_update);
 620 }
 621 
 622 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */