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

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