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     int len;
 107     const char *text;
 108 } chmod_but[BUTTONS] = {
 109     { B_SETALL, NORMAL_BUTTON, 6, 0, N_ ("Set &all") },
 110     { B_MARKED, NORMAL_BUTTON, 6, 0, N_ ("&Marked all") },
 111     { B_SETMRK, NORMAL_BUTTON, 5, 0, N_ ("S&et marked") },
 112     { B_CLRMRK, NORMAL_BUTTON, 5, 0, N_ ("C&lear marked") },
 113     { B_ENTER, DEFPUSH_BUTTON, 3, 0, N_ ("&Set") },
 114     { B_CANCEL, NORMAL_BUTTON, 3, 0, N_ ("&Cancel") },
 115 };
 116 
 117 static gboolean mode_change;
 118 static int current_file;
 119 static gboolean ignore_all;
 120 
 121 static mode_t and_mask, or_mask, ch_mode;
 122 
 123 static WLabel *statl;
 124 static WGroupbox *file_gb;
 125 
 126 /* --------------------------------------------------------------------------------------------- */
 127 /*** file scope functions ************************************************************************/
 128 /* --------------------------------------------------------------------------------------------- */
 129 
 130 static void
 131 chmod_init (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 132 {
 133     static gboolean i18n = FALSE;
 134     int i, len;
 135 
 136     for (i = 0; i < BUTTONS_PERM; i++)
 137         check_perm[i].selected = FALSE;
 138 
 139     if (i18n)
 140         return;
 141 
 142     i18n = TRUE;
 143 
 144 #ifdef ENABLE_NLS
 145     for (i = 0; i < BUTTONS_PERM; i++)
 146         check_perm[i].text = _ (check_perm[i].text);
 147 
 148     for (i = 0; i < LABELS; i++)
 149         file_info_labels[i] = _ (file_info_labels[i]);
 150 
 151     for (i = 0; i < BUTTONS; i++)
 152         chmod_but[i].text = _ (chmod_but[i].text);
 153 #endif
 154 
 155     for (i = 0; i < BUTTONS_PERM; i++)
 156     {
 157         len = str_term_width1 (check_perm[i].text);
 158         check_perm_len = MAX (check_perm_len, len);
 159     }
 160 
 161     check_perm_len += 1 + 3 + 1;  // mark, [x] and space
 162 
 163     for (i = 0; i < LABELS; i++)
 164     {
 165         len = str_term_width1 (file_info_labels[i]) + 2;  // spaces around
 166         file_info_labels_len = MAX (file_info_labels_len, len);
 167     }
 168 
 169     for (i = 0; i < BUTTONS; i++)
 170     {
 171         chmod_but[i].len = str_term_width1 (chmod_but[i].text) + 3;  // [], spaces and w/o &
 172         if (chmod_but[i].flags == DEFPUSH_BUTTON)
 173             chmod_but[i].len += 2;  // <>
 174     }
 175 }
 176 
 177 /* --------------------------------------------------------------------------------------------- */
 178 
 179 static void
 180 chmod_draw_select (const WDialog *h, int Id)
     /* [previous][next][first][last][top][bottom][index][help]  */
 181 {
 182     widget_gotoyx (h, PY + Id + 1, PX + 1);
 183     tty_print_char (check_perm[Id].selected ? '*' : ' ');
 184     widget_gotoyx (h, PY + Id + 1, PX + 3);
 185 }
 186 
 187 /* --------------------------------------------------------------------------------------------- */
 188 
 189 static void
 190 chmod_toggle_select (const WDialog *h, int Id)
     /* [previous][next][first][last][top][bottom][index][help]  */
 191 {
 192     check_perm[Id].selected = !check_perm[Id].selected;
 193     tty_setcolor (COLOR_NORMAL);
 194     chmod_draw_select (h, Id);
 195 }
 196 
 197 /* --------------------------------------------------------------------------------------------- */
 198 
 199 static void
 200 chmod_refresh (const WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 201 {
 202     int i;
 203     int y, x;
 204 
 205     tty_setcolor (COLOR_NORMAL);
 206 
 207     for (i = 0; i < BUTTONS_PERM; i++)
 208         chmod_draw_select (h, i);
 209 
 210     y = WIDGET (file_gb)->rect.y + 1;
 211     x = WIDGET (file_gb)->rect.x + 2;
 212 
 213     tty_gotoyx (y, x);
 214     tty_print_string (file_info_labels[0]);
 215     tty_gotoyx (y + 2, x);
 216     tty_print_string (file_info_labels[1]);
 217     tty_gotoyx (y + 4, x);
 218     tty_print_string (file_info_labels[2]);
 219     tty_gotoyx (y + 6, x);
 220     tty_print_string (file_info_labels[3]);
 221 }
 222 
 223 /* --------------------------------------------------------------------------------------------- */
 224 
 225 static cb_ret_t
 226 chmod_bg_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 227 {
 228     switch (msg)
 229     {
 230     case MSG_DRAW:
 231         frame_callback (w, NULL, MSG_DRAW, 0, NULL);
 232         chmod_refresh (CONST_DIALOG (w->owner));
 233         return MSG_HANDLED;
 234 
 235     default:
 236         return frame_callback (w, sender, msg, parm, data);
 237     }
 238 }
 239 
 240 /* --------------------------------------------------------------------------------------------- */
 241 
 242 static cb_ret_t
 243 chmod_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 244 {
 245     WGroup *g = GROUP (w);
 246     WDialog *h = DIALOG (w);
 247 
 248     switch (msg)
 249     {
 250     case MSG_NOTIFY:
 251     {
 252         // handle checkboxes
 253         int i;
 254 
 255         // whether notification was sent by checkbox?
 256         for (i = 0; i < BUTTONS_PERM; i++)
 257             if (sender == WIDGET (check_perm[i].check))
 258                 break;
 259 
 260         if (i < BUTTONS_PERM)
 261         {
 262             ch_mode ^= check_perm[i].mode;
 263             label_set_textv (statl, "%o", (unsigned int) ch_mode);
 264             chmod_toggle_select (h, i);
 265             mode_change = TRUE;
 266             return MSG_HANDLED;
 267         }
 268     }
 269 
 270         return MSG_NOT_HANDLED;
 271 
 272     case MSG_KEY:
 273         if (parm == 'T' || parm == 't' || parm == KEY_IC)
 274         {
 275             int i;
 276             unsigned long id;
 277 
 278             id = group_get_current_widget_id (g);
 279             for (i = 0; i < BUTTONS_PERM; i++)
 280                 if (id == WIDGET (check_perm[i].check)->id)
 281                     break;
 282 
 283             if (i < BUTTONS_PERM)
 284             {
 285                 chmod_toggle_select (h, i);
 286                 if (parm == KEY_IC)
 287                     group_select_next_widget (g);
 288                 return MSG_HANDLED;
 289             }
 290         }
 291         return MSG_NOT_HANDLED;
 292 
 293     default:
 294         return dlg_default_callback (w, sender, msg, parm, data);
 295     }
 296 }
 297 
 298 /* --------------------------------------------------------------------------------------------- */
 299 
 300 static WDialog *
 301 chmod_dlg_create (WPanel *panel, const char *fname, const struct stat *sf_stat)
     /* [previous][next][first][last][top][bottom][index][help]  */
 302 {
 303     gboolean single_set;
 304     WDialog *ch_dlg;
 305     WGroup *g;
 306     int lines, cols;
 307     int i, y;
 308     int perm_gb_len;
 309     int file_gb_len;
 310     const char *c_fname, *c_fown, *c_fgrp;
 311     char buffer[BUF_TINY];
 312 
 313     mode_change = FALSE;
 314 
 315     single_set = (panel->marked < 2);
 316     perm_gb_len = check_perm_len + 2;
 317     file_gb_len = file_info_labels_len + 2;
 318     cols = str_term_width1 (fname) + 2 + 1;
 319     file_gb_len = MAX (file_gb_len, cols);
 320 
 321     lines = single_set ? 20 : 23;
 322     cols = perm_gb_len + file_gb_len + 1 + 6;
 323 
 324     if (cols > COLS)
 325     {
 326         // shrink the right groupbox
 327         cols = COLS;
 328         file_gb_len = cols - (perm_gb_len + 1 + 6);
 329     }
 330 
 331     ch_dlg = dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, chmod_callback,
 332                          NULL, "[Chmod]", _ ("Chmod command"));
 333     g = GROUP (ch_dlg);
 334 
 335     // draw background
 336     ch_dlg->bg->callback = chmod_bg_callback;
 337 
 338     group_add_widget (g, groupbox_new (PY, PX, BUTTONS_PERM + 2, perm_gb_len, _ ("Permission")));
 339 
 340     for (i = 0; i < BUTTONS_PERM; i++)
 341     {
 342         check_perm[i].check =
 343             check_new (PY + i + 1, PX + 2, (ch_mode & check_perm[i].mode) != 0, check_perm[i].text);
 344         group_add_widget (g, check_perm[i].check);
 345     }
 346 
 347     file_gb = groupbox_new (PY, PX + perm_gb_len + 1, BUTTONS_PERM + 2, file_gb_len, _ ("File"));
 348     group_add_widget (g, file_gb);
 349 
 350     // Set the labels
 351     y = PY + 2;
 352     cols = PX + perm_gb_len + 3;
 353     c_fname = str_trunc (fname, file_gb_len - 3);
 354     group_add_widget (g, label_new (y, cols, c_fname));
 355     g_snprintf (buffer, sizeof (buffer), "%o", (unsigned int) ch_mode);
 356     statl = label_new (y + 2, cols, buffer);
 357     group_add_widget (g, statl);
 358     c_fown = str_trunc (get_owner (sf_stat->st_uid), file_gb_len - 3);
 359     group_add_widget (g, label_new (y + 4, cols, c_fown));
 360     c_fgrp = str_trunc (get_group (sf_stat->st_gid), file_gb_len - 3);
 361     group_add_widget (g, label_new (y + 6, cols, c_fgrp));
 362 
 363     if (!single_set)
 364     {
 365         i = 0;
 366 
 367         group_add_widget (g, hline_new (lines - chmod_but[i].y - 1, -1, -1));
 368 
 369         for (; i < BUTTONS - 2; i++)
 370         {
 371             y = lines - chmod_but[i].y;
 372             group_add_widget (g,
 373                               button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chmod_but[i].len,
 374                                           chmod_but[i].ret_cmd, chmod_but[i].flags,
 375                                           chmod_but[i].text, NULL));
 376             i++;
 377             group_add_widget (g,
 378                               button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1,
 379                                           chmod_but[i].ret_cmd, chmod_but[i].flags,
 380                                           chmod_but[i].text, NULL));
 381         }
 382     }
 383 
 384     i = BUTTONS - 2;
 385     y = lines - chmod_but[i].y;
 386     group_add_widget (g, hline_new (y - 1, -1, -1));
 387     group_add_widget (g,
 388                       button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chmod_but[i].len,
 389                                   chmod_but[i].ret_cmd, chmod_but[i].flags, chmod_but[i].text,
 390                                   NULL));
 391     i++;
 392     group_add_widget (g,
 393                       button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1, chmod_but[i].ret_cmd,
 394                                   chmod_but[i].flags, chmod_but[i].text, NULL));
 395 
 396     // select first checkbox
 397     widget_select (WIDGET (check_perm[0].check));
 398 
 399     return ch_dlg;
 400 }
 401 
 402 /* --------------------------------------------------------------------------------------------- */
 403 
 404 static void
 405 chmod_done (gboolean need_update)
     /* [previous][next][first][last][top][bottom][index][help]  */
 406 {
 407     if (need_update)
 408         update_panels (UP_OPTIMIZE, UP_KEEPSEL);
 409     repaint_screen ();
 410 }
 411 
 412 /* --------------------------------------------------------------------------------------------- */
 413 
 414 static gboolean
 415 try_chmod (const vfs_path_t *p, mode_t m)
     /* [previous][next][first][last][top][bottom][index][help]  */
 416 {
 417     const char *fname = NULL;
 418 
 419     while (mc_chmod (p, m) == -1 && !ignore_all)
 420     {
 421         int my_errno = errno;
 422 
 423         if (fname == NULL)
 424             fname = x_basename (vfs_path_as_str (p));
 425 
 426         errno = my_errno;  // restore errno for file_error(
 427 
 428         switch (file_error (NULL, TRUE, _ ("Cannot chmod\n%sn%s"), fname))
 429         {
 430         case FILE_IGNORE:
 431             // try next file
 432             return TRUE;
 433 
 434         case FILE_IGNORE_ALL:
 435             ignore_all = TRUE;
 436             // try next file
 437             return TRUE;
 438 
 439         case FILE_RETRY:
 440             // retry this file
 441             break;
 442 
 443         case FILE_ABORT:
 444         default:
 445             // stop remain files processing
 446             return FALSE;
 447         }
 448     }
 449 
 450     return TRUE;
 451 }
 452 
 453 /* --------------------------------------------------------------------------------------------- */
 454 
 455 static gboolean
 456 do_chmod (WPanel *panel, const vfs_path_t *p, struct stat *sf)
     /* [previous][next][first][last][top][bottom][index][help]  */
 457 {
 458     gboolean ret;
 459 
 460     sf->st_mode &= and_mask;
 461     sf->st_mode |= or_mask;
 462 
 463     ret = try_chmod (p, sf->st_mode);
 464 
 465     do_file_mark (panel, current_file, 0);
 466 
 467     return ret;
 468 }
 469 
 470 /* --------------------------------------------------------------------------------------------- */
 471 
 472 static void
 473 apply_mask (WPanel *panel, vfs_path_t *vpath, struct stat *sf)
     /* [previous][next][first][last][top][bottom][index][help]  */
 474 {
 475     gboolean ok;
 476 
 477     if (!do_chmod (panel, vpath, sf))
 478         return;
 479 
 480     do
 481     {
 482         const GString *fname;
 483 
 484         fname = panel_find_marked_file (panel, &current_file);
 485         vpath = vfs_path_from_str (fname->str);
 486         ok = (mc_stat (vpath, sf) == 0);
 487 
 488         if (!ok)
 489         {
 490             // if current file was deleted outside mc -- try next file
 491             // decrease panel->marked
 492             do_file_mark (panel, current_file, 0);
 493 
 494             // try next file
 495             ok = TRUE;
 496         }
 497         else
 498         {
 499             ch_mode = sf->st_mode;
 500 
 501             ok = do_chmod (panel, vpath, sf);
 502         }
 503 
 504         vfs_path_free (vpath, TRUE);
 505     }
 506     while (ok && panel->marked != 0);
 507 }
 508 
 509 /* --------------------------------------------------------------------------------------------- */
 510 /*** public functions ****************************************************************************/
 511 /* --------------------------------------------------------------------------------------------- */
 512 
 513 void
 514 chmod_cmd (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
 515 {
 516     gboolean need_update;
 517     gboolean end_chmod;
 518 
 519     chmod_init ();
 520 
 521     current_file = 0;
 522     ignore_all = FALSE;
 523 
 524     do
 525     {  // do while any files remaining
 526         vfs_path_t *vpath;
 527         WDialog *ch_dlg;
 528         struct stat sf_stat;
 529         const GString *fname;
 530         int i, result;
 531 
 532         do_refresh ();
 533 
 534         need_update = FALSE;
 535         end_chmod = FALSE;
 536 
 537         fname = panel_get_marked_file (panel, &current_file);
 538         if (fname == NULL)
 539             break;
 540 
 541         vpath = vfs_path_from_str (fname->str);
 542 
 543         if (mc_stat (vpath, &sf_stat) != 0)
 544         {
 545             vfs_path_free (vpath, TRUE);
 546             break;
 547         }
 548 
 549         ch_mode = sf_stat.st_mode;
 550 
 551         ch_dlg = chmod_dlg_create (panel, fname->str, &sf_stat);
 552         result = dlg_run (ch_dlg);
 553 
 554         switch (result)
 555         {
 556         case B_CANCEL:
 557             end_chmod = TRUE;
 558             break;
 559 
 560         case B_ENTER:
 561             if (mode_change)
 562             {
 563                 if (panel->marked <= 1)
 564                 {
 565                     // single or last file
 566                     if (mc_chmod (vpath, ch_mode) == -1 && !ignore_all)
 567                         file_error_message (_ ("Cannot chmod\n%s"), fname->str);
 568                     end_chmod = TRUE;
 569                 }
 570                 else if (!try_chmod (vpath, ch_mode))
 571                 {
 572                     // stop multiple files processing
 573                     result = B_CANCEL;
 574                     end_chmod = TRUE;
 575                 }
 576             }
 577 
 578             need_update = TRUE;
 579             break;
 580 
 581         case B_SETALL:
 582         case B_MARKED:
 583             and_mask = or_mask = 0;
 584             and_mask = ~and_mask;
 585 
 586             for (i = 0; i < BUTTONS_PERM; i++)
 587                 if (check_perm[i].selected || result == B_SETALL)
 588                 {
 589                     if (check_perm[i].check->state)
 590                         or_mask |= check_perm[i].mode;
 591                     else
 592                         and_mask &= ~check_perm[i].mode;
 593                 }
 594 
 595             apply_mask (panel, vpath, &sf_stat);
 596             need_update = TRUE;
 597             end_chmod = TRUE;
 598             break;
 599 
 600         case B_SETMRK:
 601             and_mask = or_mask = 0;
 602             and_mask = ~and_mask;
 603 
 604             for (i = 0; i < BUTTONS_PERM; i++)
 605                 if (check_perm[i].selected)
 606                     or_mask |= check_perm[i].mode;
 607 
 608             apply_mask (panel, vpath, &sf_stat);
 609             need_update = TRUE;
 610             end_chmod = TRUE;
 611             break;
 612 
 613         case B_CLRMRK:
 614             and_mask = or_mask = 0;
 615             and_mask = ~and_mask;
 616 
 617             for (i = 0; i < BUTTONS_PERM; i++)
 618                 if (check_perm[i].selected)
 619                     and_mask &= ~check_perm[i].mode;
 620 
 621             apply_mask (panel, vpath, &sf_stat);
 622             need_update = TRUE;
 623             end_chmod = TRUE;
 624             break;
 625 
 626         default:
 627             break;
 628         }
 629 
 630         if (panel->marked != 0 && result != B_CANCEL)
 631         {
 632             do_file_mark (panel, current_file, 0);
 633             need_update = TRUE;
 634         }
 635 
 636         vfs_path_free (vpath, TRUE);
 637 
 638         widget_destroy (WIDGET (ch_dlg));
 639     }
 640     while (panel->marked != 0 && !end_chmod);
 641 
 642     chmod_done (need_update);
 643 }
 644 
 645 /* --------------------------------------------------------------------------------------------- */

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