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

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