1 /*
2  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3  *
4  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5  *                                  and others.
6  *
7  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8  * Portions Copyright (C) 1989-1992, Brian Berliner
9  *
10  * You may distribute under the terms of the GNU General Public License as
11  * specified in the README file that comes with the CVS source distribution.
12  *
13  * Remove a File
14  *
15  * Removes entries from the present version. The entries will be removed from
16  * the RCS repository upon the next "commit".
17  *
18  * "remove" accepts no options, only file names that are to be removed.  The
19  * file must not exist in the current directory for "remove" to work
20  * correctly.
21  */
22 #include <sys/cdefs.h>
23 __RCSID("$NetBSD: remove.c,v 1.4 2016/05/17 14:00:09 christos Exp $");
24 
25 #include "cvs.h"
26 
27 #ifdef CLIENT_SUPPORT
28 static int remove_force_fileproc (void *callerdat,
29                                                    struct file_info *finfo);
30 #endif
31 static int remove_fileproc (void *callerdat, struct file_info *finfo);
32 static Dtype remove_dirproc (void *callerdat, const char *dir,
33                              const char *repos, const char *update_dir,
34                              List *entries);
35 
36 static int force;
37 static int local;
38 static int removed_files;
39 static int existing_files;
40 
41 static const char *const remove_usage[] =
42 {
43     "Usage: %s %s [-flR] [files...]\n",
44     "\t-f\tDelete the file before removing it.\n",
45     "\t-l\tProcess this directory only (not recursive).\n",
46     "\t-R\tProcess directories recursively.\n",
47     "(Specify the --help global option for a list of other help options)\n",
48     NULL
49 };
50 
51 int
cvsremove(int argc,char ** argv)52 cvsremove (int argc, char **argv)
53 {
54     int c, err;
55 
56     if (argc == -1)
57           usage (remove_usage);
58 
59     getoptreset ();
60     while ((c = getopt (argc, argv, "+flR")) != -1)
61     {
62           switch (c)
63           {
64               case 'f':
65                     force = 1;
66                     break;
67               case 'l':
68                     local = 1;
69                     break;
70               case 'R':
71                     local = 0;
72                     break;
73               case '?':
74               default:
75                     usage (remove_usage);
76                     break;
77           }
78     }
79     argc -= optind;
80     argv += optind;
81 
82     wrap_setup ();
83 
84 #ifdef CLIENT_SUPPORT
85     if (current_parsed_root->isremote) {
86           /* Call expand_wild so that the local removal of files will
87            work.  It's ok to do it always because we have to send the
88            file names expanded anyway.  */
89           expand_wild (argc, argv, &argc, &argv);
90 
91           if (force)
92           {
93               if (!noexec)
94               {
95                     start_recursion (remove_force_fileproc, NULL, NULL, NULL,
96                                          NULL, argc, argv, local, W_LOCAL,
97                                          0, CVS_LOCK_NONE, NULL, 0, NULL);
98               }
99               /* else FIXME should probably act as if the file doesn't exist
100                  in doing the following checks.  */
101           }
102 
103           start_server ();
104           ign_setup ();
105           if (local)
106               send_arg("-l");
107           send_arg ("--");
108           /* FIXME: Can't we set SEND_NO_CONTENTS here?  Needs investigation.  */
109           send_files (argc, argv, local, 0, 0);
110           send_file_names (argc, argv, 0);
111           free_names (&argc, argv);
112           send_to_server ("remove\012", 0);
113         return get_responses_and_close ();
114     }
115 #endif
116 
117     /* start the recursion processor */
118     err = start_recursion (remove_fileproc, NULL, remove_dirproc, NULL,
119                                  NULL, argc, argv, local, W_LOCAL, 0,
120                                  CVS_LOCK_READ, NULL, 1, NULL);
121 
122     if (removed_files && !really_quiet)
123           error (0, 0, "use `%s commit' to remove %s permanently", program_name,
124                  (removed_files == 1) ? "this file" : "these files");
125 
126     if (existing_files)
127           error (0, 0,
128                  ((existing_files == 1) ?
129                     "%d file exists; remove it first" :
130                     "%d files exist; remove them first"),
131                  existing_files);
132 
133     return (err);
134 }
135 
136 #ifdef CLIENT_SUPPORT
137 
138 /*
139  * This is called via start_recursion if we are running as the client
140  * and the -f option was used.  We just physically remove the file.
141  */
142 
143 /*ARGSUSED*/
144 static int
remove_force_fileproc(void * callerdat,struct file_info * finfo)145 remove_force_fileproc (void *callerdat, struct file_info *finfo)
146 {
147     if (CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
148           error (0, errno, "unable to remove %s", finfo->fullname);
149     return 0;
150 }
151 
152 #endif
153 
154 /*
155  * remove the file, only if it has already been physically removed
156  */
157 /* ARGSUSED */
158 static int
remove_fileproc(void * callerdat,struct file_info * finfo)159 remove_fileproc (void *callerdat, struct file_info *finfo)
160 {
161     Vers_TS *vers;
162 
163     if (force)
164     {
165           if (!noexec)
166           {
167               if ( CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
168               {
169                     error (0, errno, "unable to remove %s", finfo->fullname);
170               }
171           }
172           /* else FIXME should probably act as if the file doesn't exist
173              in doing the following checks.  */
174     }
175 
176     vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
177 
178     if (vers->ts_user != NULL)
179     {
180           existing_files++;
181           if (!quiet)
182               error (0, 0, "file `%s' still in working directory",
183                        finfo->fullname);
184     }
185     else if (vers->vn_user == NULL)
186     {
187           if (!quiet)
188               error (0, 0, "nothing known about `%s'", finfo->fullname);
189     }
190     else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
191     {
192           char *fname;
193 
194           /*
195            * It's a file that has been added, but not commited yet. So,
196            * remove the ,t file for it and scratch it from the
197            * entries file.  */
198           Scratch_Entry (finfo->entries, finfo->file);
199           fname = Xasprintf ("%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
200           if (unlink_file (fname) < 0
201               && !existence_error (errno))
202               error (0, errno, "cannot remove %s", CVSEXT_LOG);
203           if (!quiet)
204               error (0, 0, "removed `%s'", finfo->fullname);
205 
206 #ifdef SERVER_SUPPORT
207           if (server_active)
208               server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
209 #endif
210           free (fname);
211     }
212     else if (vers->vn_user[0] == '-')
213     {
214           if (!quiet)
215               error (0, 0, "file `%s' already scheduled for removal",
216                        finfo->fullname);
217     }
218     else if (vers->tag != NULL && isdigit ((unsigned char) *vers->tag))
219     {
220           /* Commit will just give an error, and so there seems to be
221              little reason to allow the remove.  I mean, conflicts that
222              arise out of parallel development are one thing, but conflicts
223              that arise from sticky tags are quite another.
224 
225              I would have thought that non-branch sticky tags should be the
226              same but at least now, removing a file with a non-branch sticky
227              tag means to delete the tag from the file.  I'm not sure that
228              is a good behavior, but until it is changed, we need to allow
229              it.  */
230           error (0, 0, "\
231 cannot remove file `%s' which has a numeric sticky tag of `%s'",
232                  finfo->fullname, vers->tag);
233     }
234     else if (vers->date != NULL)
235     {
236           /* Commit will just give an error, and so there seems to be
237              little reason to allow the remove.  */
238           error (0, 0, "\
239 cannot remove file `%s' which has a sticky date of `%s'",
240                  finfo->fullname, vers->date);
241     }
242     else
243     {
244           char *fname;
245 
246 /* cvsacl patch */
247 #ifdef SERVER_SUPPORT
248           if (use_cvs_acl /* && server_active */)
249           {
250               if (!access_allowed (finfo->file, finfo->repository, vers->tag, 7,
251                                          NULL, NULL, 1))
252               {
253                     if (stop_at_first_permission_denied)
254                         error (1, 0, "permission denied for %s",
255                                  Short_Repository (finfo->repository));
256                     else
257                         error (0, 0, "permission denied for %s/%s",
258                                  Short_Repository (finfo->repository), finfo->file);
259 
260                     return (0);
261               }
262           }
263 #endif
264 
265           /* Re-register it with a negative version number.  */
266           fname = Xasprintf ("-%s", vers->vn_user);
267           Register (finfo->entries, finfo->file, fname, vers->ts_rcs,
268                       vers->options, vers->tag, vers->date, vers->ts_conflict);
269           if (!quiet)
270               error (0, 0, "scheduling `%s' for removal", finfo->fullname);
271           removed_files++;
272 
273 #ifdef SERVER_SUPPORT
274           if (server_active)
275               server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
276 #endif
277           free (fname);
278     }
279 
280     freevers_ts (&vers);
281     return (0);
282 }
283 
284 
285 
286 /*
287  * Print a warm fuzzy message
288  */
289 /* ARGSUSED */
290 static Dtype
remove_dirproc(void * callerdat,const char * dir,const char * repos,const char * update_dir,List * entries)291 remove_dirproc (void *callerdat, const char *dir, const char *repos,
292                 const char *update_dir, List *entries)
293 {
294     if (!quiet)
295           error (0, 0, "Removing %s", update_dir);
296     return (R_PROCESS);
297 }
298