1 /*
2 ** 1998-05-29 -	A command to change the access flags of a file or directory.
3 **		Made significantly simpler by the new cmd_generic module.
4 ** 1999-03-06 -	Adapted for new selection/generic handling.
5 ** 2010-03-02 -	GIO porting more or less complete.
6 */
7 
8 #include "gentoo.h"
9 #include "errors.h"
10 #include "dirpane.h"
11 #include "strutil.h"
12 #include "window.h"
13 
14 #include "cmd_generic.h"
15 #include "cmd_chmod.h"
16 
17 #define	CMD_ID	"chmod"
18 
19 /* ----------------------------------------------------------------------------------------- */
20 
21 typedef struct {
22 	GtkWidget	*frame;
23 	GtkWidget	*vbox;
24 	GtkWidget	*check[3];
25 	gulong		signal[3];
26 } PFrame;
27 
28 typedef struct {
29 	GtkWidget	*vbox;
30 	GtkWidget	*label;
31 	GtkWidget	*fbox;
32 	PFrame		frame[4];
33 	GtkWidget	*entry_text;
34 	GtkWidget	*entry_octal;
35 	GtkWidget	*bbox;
36 	GtkWidget	*all, *none, *toggle, *revert;
37 	mode_t		last_mode;
38 	GtkWidget	*recurse;
39 	gboolean	last_recurse;
40 	GtkWidget	*nodirs;
41 	gboolean	last_nodirs;
42 } ChmInfo;
43 
44 static const mode_t mask[] = {  S_ISUID, S_ISGID, S_ISVTX,
45 				S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH };
46 
47 /* ----------------------------------------------------------------------------------------- */
48 
get_checks(const ChmInfo * chm)49 static mode_t get_checks(const ChmInfo *chm)
50 {
51 	mode_t	mode = 0U;
52 	guint	i, j, k;
53 
54 	for(i = k = 0; i < sizeof chm->frame / sizeof *chm->frame; i++)
55 	{
56 		for(j = 0; j < sizeof chm->frame[i].check / sizeof *chm->frame[i].check; j++, k++)
57 		{
58 			if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chm->frame[i].check[j])))
59 				mode |= mask[k];
60 		}
61 	}
62 	return mode;
63 }
64 
65 /* 2009-03-25 -	Update the textual representations. These are currently read-only. */
set_texts(ChmInfo * chm,mode_t mode)66 static void set_texts(ChmInfo *chm, mode_t mode)
67 {
68 	gchar	buf[32];
69 
70 	stu_mode_to_text(buf, sizeof buf, mode);
71 	gtk_entry_set_text(GTK_ENTRY(chm->entry_text), buf + 1);	/* Skip the directory indicator. */
72 	g_snprintf(buf, sizeof buf, "%o", mode);
73 	gtk_entry_set_text(GTK_ENTRY(chm->entry_octal), buf);
74 }
75 
set_checks(ChmInfo * chm,mode_t mode)76 static void set_checks(ChmInfo *chm, mode_t mode)
77 {
78 	guint	i, j, k;
79 
80 	for(i = k = 0; i < sizeof chm->frame / sizeof *chm->frame; i++)
81 	{
82 		for(j = 0; j < sizeof chm->frame[i].check / sizeof *chm->frame[i].check; j++, k++)
83 		{
84 			g_signal_handler_block(G_OBJECT(chm->frame[i].check[j]), chm->frame[i].signal[j]);
85 			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chm->frame[i].check[j]), mode & mask[k]);
86 			g_signal_handler_unblock(G_OBJECT(chm->frame[i].check[j]), chm->frame[i].signal[j]);
87 		}
88 	}
89 	set_texts(chm, mode);
90 }
91 
chm_body(MainInfo * min,DirPane * src,DirRow2 * row,gpointer gen,gpointer user)92 static void chm_body(MainInfo *min, DirPane *src, DirRow2 *row, gpointer gen, gpointer user)
93 {
94 	ChmInfo		*chm = user;
95 	gchar		temp[2 * FILENAME_MAX + 128];
96 	mode_t		mode = dp_row_get_mode(dp_get_tree_model(src), row);
97 
98 	g_snprintf(temp, sizeof temp, _("Set protection bits for \"%s\":"), dp_row_get_name_display(dp_get_tree_model(src), row));
99 	gtk_label_set_text(GTK_LABEL(chm->label), temp);
100 	set_checks(chm, chm->last_mode = (mode & 07777));
101 }
102 
103 /* ----------------------------------------------------------------------------------------- */
104 
chmod_gfile(MainInfo * min,DirPane * src,const GFile * file,mode_t mode,gboolean recurse,gboolean nodirs,GError ** err)105 static gboolean chmod_gfile(MainInfo *min, DirPane *src, const GFile *file, mode_t mode, gboolean recurse, gboolean nodirs, GError **err)
106 {
107 	GFileInfo	*fi;
108 	gboolean	ok = FALSE;
109 
110 	if((fi = g_file_query_info((GFile *) file, G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_UNIX_MODE, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, err)) != NULL)
111 	{
112 		const gboolean	isdir = (g_file_info_get_file_type(fi) == G_FILE_TYPE_DIRECTORY);
113 
114 		/* Don't try to change the file's type. */
115 		g_file_info_remove_attribute(fi, G_FILE_ATTRIBUTE_STANDARD_TYPE);
116 
117 		/* If non-directory or we're attacking dirs, set the mode first. */
118 		if(!isdir || !nodirs)
119 		{
120 			g_file_info_set_attribute_uint32(fi, G_FILE_ATTRIBUTE_UNIX_MODE, mode);
121 			ok = g_file_set_attributes_from_info((GFile *) file, fi, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, err);
122 		}
123 		else
124 			ok = TRUE;
125 		/* Drop the info, we're done. */
126 		g_object_unref(fi);
127 
128 		/* If successful so far, consider recursing. */
129 		if(ok && isdir && recurse)
130 		{
131 			GFileEnumerator	*fe;
132 
133 			if((fe = g_file_enumerate_children((GFile *) file, "standard::name,unix::mode", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, err)) != NULL)
134 			{
135 				GFileInfo	*cfi;
136 				GFile		*child;
137 
138 				while(ok && (cfi = g_file_enumerator_next_file(fe, NULL, err)) != NULL)
139 				{
140 					if((child = g_file_get_child((GFile *) file, g_file_info_get_name(cfi))) != NULL)
141 					{
142 						ok = chmod_gfile(min, src, child, mode, recurse, nodirs, err);
143 						g_object_unref(child);
144 					}
145 					else
146 						ok = FALSE;
147 					g_object_unref(cfi);
148 				}
149 				g_object_unref(fe);
150 			}
151 			else
152 				ok = FALSE;
153 		}
154 	}
155 	return ok;
156 }
157 
chm_action(MainInfo * min,DirPane * src,DirPane * dst,DirRow2 * row,GError ** err,gpointer user)158 static gint chm_action(MainInfo *min, DirPane *src, DirPane *dst, DirRow2 *row, GError **err, gpointer user)
159 {
160 	ChmInfo		*chm = user;
161 	const mode_t	mode = get_checks(chm);
162 	GFile		*dfile;
163 	gboolean	ok;
164 
165 	chm->last_recurse = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chm->recurse));
166 	chm->last_nodirs  = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chm->nodirs));
167 	dfile = dp_get_file_from_row(src, row);
168 	ok = chmod_gfile(min, src, dfile, mode, chm->last_recurse, chm->last_nodirs, err);
169 	if(ok)
170 		dp_unselect(src, row);
171 
172 	return ok;
173 }
174 
175 /* ----------------------------------------------------------------------------------------- */
176 
evt_clicked(GtkWidget * wid,gpointer user)177 static void evt_clicked(GtkWidget *wid, gpointer user)
178 {
179 	ChmInfo	*chm = user;
180 
181 	if(wid == chm->all)
182 		set_checks(chm, S_ISUID | S_ISGID | S_ISVTX | S_IRUSR | S_IWUSR | S_IXUSR |
183 				S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH);
184 	else if(wid == chm->none)
185 		set_checks(chm, 0);
186 	else if(wid == chm->toggle)
187 		set_checks(chm, (~get_checks(chm)) & 07777);
188 	else if(wid == chm->revert)
189 		set_checks(chm, chm->last_mode);
190 	else
191 		set_texts(chm, get_checks(chm));
192 }
193 
194 /* 1998-05-29 -	Build a protection frame, with three checkboxes. If type is 0, we build a
195 **		special one (with setuid/setgid/sticky), otherwise a standard (read/write/exec).
196 */
build_frame(ChmInfo * ci,gint pos,gint type)197 static void build_frame(ChmInfo *ci, gint pos, gint type)
198 {
199 	gchar	*label[] = { N_("Special"), N_("Owner"),   N_("Group"),  N_("Others") };
200 	gchar	*check[] = { N_("Set UID"), N_("Set GID"), N_("Sticky"), N_("Read"), N_("Write"), N_("Execute") };
201 	PFrame	*fr = &ci->frame[pos];
202 	gint	i;
203 
204 	fr->frame = gtk_frame_new(_(label[pos]));
205 	fr->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
206 	for(i = 0; i < 3; i++)
207 	{
208 		fr->check[i] = gtk_check_button_new_with_label(_(check[type * 3 + i]));
209 		fr->signal[i] = g_signal_connect(G_OBJECT(fr->check[i]), "toggled", G_CALLBACK(evt_clicked), ci);
210 		gtk_box_pack_start(GTK_BOX(fr->vbox), fr->check[i], TRUE, TRUE, 0);
211 	}
212 	gtk_container_add(GTK_CONTAINER(fr->frame), fr->vbox);
213 }
214 
cmd_chmod(MainInfo * min,DirPane * src,DirPane * dst,const CmdArg * ca)215 gint cmd_chmod(MainInfo *min, DirPane *src, DirPane *dst, const CmdArg *ca)
216 {
217 	static ChmInfo	ci;
218 	guint		i;
219 	GtkWidget	*hbox, *w;
220 
221 	ci.vbox  = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
222 	ci.label = gtk_label_new(_("Protection Bits"));
223 	ci.fbox  = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
224 	for(i = 0; i < sizeof ci.frame / sizeof ci.frame[0]; i++)
225 	{
226 		build_frame(&ci, i, (i == 0) ? 0 : 1);
227 		gtk_box_pack_start(GTK_BOX(ci.fbox), ci.frame[i].frame, TRUE, TRUE, 5);
228 	}
229 	gtk_box_pack_start(GTK_BOX(ci.vbox), ci.label, FALSE, FALSE, 0);
230 	gtk_box_pack_start(GTK_BOX(ci.vbox), ci.fbox,  TRUE,  TRUE, 0);
231 
232 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
233 	w = gtk_label_new(_("Textual"));
234 	gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 0);
235 	ci.entry_text = gtk_entry_new();
236 	gtk_entry_set_max_length(GTK_ENTRY(ci.entry_text), 10);
237 	gtk_entry_set_width_chars(GTK_ENTRY(ci.entry_text), 10);
238 	gtk_editable_set_editable(GTK_EDITABLE(ci.entry_text), FALSE);
239 	gtk_box_pack_start(GTK_BOX(hbox), ci.entry_text, FALSE, FALSE, 0);
240 
241 	ci.entry_octal = gtk_entry_new();
242 	gtk_entry_set_max_length(GTK_ENTRY(ci.entry_octal),  4);
243 	gtk_entry_set_width_chars(GTK_ENTRY(ci.entry_octal), 4);
244 	gtk_editable_set_editable(GTK_EDITABLE(ci.entry_octal), FALSE);
245 	gtk_box_pack_end(GTK_BOX(hbox), ci.entry_octal, FALSE, FALSE, 0);
246 	w = gtk_label_new(_("Octal"));
247 	gtk_box_pack_end(GTK_BOX(hbox), w, FALSE, FALSE, 0);
248 	gtk_box_pack_start(GTK_BOX(ci.vbox), hbox, FALSE, FALSE, 0);
249 
250 	ci.bbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
251 	ci.all	  = gtk_button_new_with_label(_("All"));
252 	ci.none	  = gtk_button_new_with_label(_("None"));
253 	ci.toggle = gtk_button_new_with_label(_("Toggle"));
254 	ci.revert = gtk_button_new_with_label(_("Revert"));
255 
256 	g_signal_connect(G_OBJECT(ci.all),    "clicked", G_CALLBACK(evt_clicked), &ci);
257 	g_signal_connect(G_OBJECT(ci.none),   "clicked", G_CALLBACK(evt_clicked), &ci);
258 	g_signal_connect(G_OBJECT(ci.toggle), "clicked", G_CALLBACK(evt_clicked), &ci);
259 	g_signal_connect(G_OBJECT(ci.revert), "clicked", G_CALLBACK(evt_clicked), &ci);
260 	gtk_box_pack_start(GTK_BOX(ci.bbox), ci.all,    TRUE, TRUE, 5);
261 	gtk_box_pack_start(GTK_BOX(ci.bbox), ci.none,   TRUE, TRUE, 5);
262 	gtk_box_pack_start(GTK_BOX(ci.bbox), ci.toggle, TRUE, TRUE, 5);
263 	gtk_box_pack_start(GTK_BOX(ci.bbox), ci.revert, TRUE, TRUE, 5);
264 	gtk_box_pack_start(GTK_BOX(ci.vbox), ci.bbox, TRUE, TRUE, 0);
265 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
266 	ci.recurse = gtk_check_button_new_with_label(_("Recurse Directories?"));
267 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ci.recurse), ci.last_recurse);
268 	gtk_box_pack_start(GTK_BOX(hbox), ci.recurse, TRUE, TRUE, 0);
269 	ci.nodirs = gtk_check_button_new_with_label(_("Don't Touch Directories?"));
270 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ci.nodirs), ci.last_nodirs);
271 	gtk_box_pack_start(GTK_BOX(hbox), ci.nodirs, TRUE, TRUE, 0);
272 	gtk_box_pack_start(GTK_BOX(ci.vbox), hbox, FALSE, FALSE, 0);
273 
274 	return cmd_generic(min, _("Change Mode"), CGF_SRC, chm_body, chm_action, NULL, &ci);
275 }
276