Going root when writing to file/saving file? - emacs

Is it possible to open a file(in root location) as non-root user in Emacs, edit it and then when its time to save provide the password so the Emacs can get write to the file? Better still provide different buffers with different user privileges?
I know of Tramp but couldn't get my head around it.

Here's how I do it:
(require 'tramp)
(defun sudired ()
(interactive)
(dired "/sudo::/"))
You'll get a dired buffer where you have root privileges.
Any subsequent directory or file that you open from here will be with root.
Any other dired buffers will not be affected.

Update: I now use sudo-edit (available on Melpa or at https://github.com/nflath/sudo-edit), which has the header warning and is more robust than this function.
This is what I use. You can open a file (even one that doesn't exist yet) or directory as a normal user, and run this function to get root privileges.
(defun find-alternative-file-with-sudo ()
(interactive)
(let ((bname (expand-file-name (or buffer-file-name
default-directory)))
(pt (point)))
(setq bname (or (file-remote-p bname 'localname)
(concat "/sudo::" bname)))
(cl-flet ((server-buffer-done
(buffer &optional for-killing)
nil))
(find-alternate-file bname))
(goto-char pt)))
I also have this, which makes a big red banner across the top of the buffer telling me it's opened as root.
(defface find-file-root-header-face
'((t (:foreground "white" :background "red3")))
"*Face use to display header-lines for files opened as root.")
(defun find-file-root-header-warning ()
"*Display a warning in header line of the current buffer.
This function is suitable to add to `find-file-hook'."
(when (string-equal
(file-remote-p (or buffer-file-name default-directory) 'user)
"root")
(let* ((warning "WARNING: EDITING FILE AS ROOT!")
(space (+ 6 (- (window-width) (length warning))))
(bracket (make-string (/ space 2) ?-))
(warning (concat bracket warning bracket)))
(setq header-line-format
(propertize warning 'face 'find-file-root-header-face)))))
(add-hook 'find-file-hook 'find-file-root-header-warning)
(add-hook 'dired-mode-hook 'find-file-root-header-warning)

You don't need any special functions for this, it's built-in to Emacs (at least it is for version 24).
To open a file as root:
C-x C-f to open the find-file dialog in the minibuffer.
Then prepend /su::/ to the file path:
/su::/path/to/root/file
You'll be prompted for the root password. After that, you can open the file as if you are root. The rest of your buffers will be unaffected. However, if you open another file from the same buffer, you'll automatically be opening it as root.

I wanted to have a way to open root files too, so I came up with this function that replaced build in find-file, now I have this in my .emacs:
(defun test (&rest args)
(with-temp-buffer
(eq (apply 'call-process "test" nil (current-buffer) nil args) 0)))
(defun have-permission (filename)
;; only bash expand ~ with home directory
(let ((expanded (replace-regexp-in-string "~"
(concat "/home/" (user-real-login-name))
filename)))
(if (not (file-exists-p expanded))
(let ((directory (file-name-directory expanded)))
(and (test "-r" directory) (test "-x" directory) (test "-w" directory)))
(and (test "-r" expanded) (test "-w" expanded)))))
(defun find-every-file (filename &optional wildcards)
"Open file use sudo:: if user have no permissions to open the file"
(interactive
(find-file-read-args "Find All Files: "
(confirm-nonexistent-file-or-buffer)))
(find-file (if (have-permission filename)
filename
;; you can replace that with /su:: if you don't have sudo access
(concat "/sudo::" (file-truename filename)))))
(global-set-key (kbd "C-x C-f") 'find-every-file)
It also work if you try to open non existing file or non existing file in directory you don't have write permissions.
you can combine it with #jpkotta warning popup.

Related

Update gtag file in emacs for a single file once on saving

My configuration is copied from http://www.emacswiki.org/emacs/GnuGlobal#toc4 . The purpose is to update existing tag file once i save source file. But the result is the same as expected.
The full configuration is:
(gtags-mode 1)
(defun gtags-root-dir ()
"Returns GTAGS root directory or nil if doesn't exist."
(with-temp-buffer
(if (zerop (call-process "global" nil t nil "-pr"))
(buffer-substring (point-min) (1- (point-max)))
nil)))
(defun gtags-update-single(filename)
"Update Gtags database for changes in a single file"
(interactive)
(start-process "update-gtags" "update-gtags" "bash" "-c" (concat "cd " (gtags-root-dir) " ; gtags --single-update " filename )))
(defun gtags-update-current-file()
(interactive)
(defvar filename)
(setq filename (replace-regexp-in-string (gtags-root-dir) "." (buffer-file-name (current-buffer))))
(gtags-update-single filename)
(message "Gtags updated for %s" filename))
(defun gtags-update-hook()
"Update GTAGS file (insert )ncrementally upon saving a file"
(when gtags-mode ;;It is copy past error..
(when (gtags-root-dir)
(gtags-update-current-file))))
(add-hook 'after-save-hook 'gtags-update-hook)
Update
My understanding is that tags will be update via command
(gtags-update-single filename)
(message "Gtags updated for %s" filename))
once file in buffer is saved. That means a new added or renamed or removed function will be updated to the tag file. In my test, I do see output message(The tags is in ededemo directory ):
Wrote /other/projectbase/cplusproject/ededemo/src/main.cpp
Gtags updated for ./src/main.cpp
each time function is renamed or added after c-x c-s. But M-x gtags-find-tag could not find my new added function. Is there any wrong in understanding?
This line is clearly responsible/broken:
(when gtags-;-*- mode: ${1:mode} -*-
Looking at the Wiki page, I can't fathom how you managed to end up with that.
The docstring comment is also corrupted. Just copy the whole function again.

Spawing ctags from Emacs with start-process

I have a function that is supposed to create a ctags file and load it into Emacs asynchronously. ctags can take a while to run if invoked on really big files, and I don't want my function to do any blocking, thus I use start-process. This is what it all looks like:
(defun temp-tags-file-for-file (file)
"Generate a temporary tags file for FILE.
Add the file to tags list and return the name of the file."
(if (not (boundp 'ctags-command))
(setq ctags-command "/usr/bin/ctags"))
(let* ((temp-file (make-temp-file "EMACS_TAGS"))
(proc (start-process "temp-tags-proc" nil ctags-command
"-f" temp-file file)))
(set-process-sentinel proc
(lambda (proc msg)
(when (eq (process-status proc) 'exit)
(if (boundp 'temp-tags-file)
(progn
(add-to-list 'tags-table-list
temp-tags-file)
(makunbound 'temp-tags-file))))))
(setq temp-tags-file temp-file)
temp-file))
For some reason, the tags file is always blank. Calling ctags with the exact same parameters from the shell generates a non-blank, working tags file. How do I get ctags to print its output properly?
If ctags wants the shell, just give it to the shell:
(start-process "temp-tags-proc" nil shell-file-name shell-command-switch
(format "/usr/bin/ctags ~/Dropbox/source/c/*.c -f %s"
(make-temp-file "EMACS_TAGS")))

Waiting on compilation to finish

I am using compile to pull new files from source tree using mercurial "hg pull".
I am performing a save of all buffers before the pull and would like to "refresh all opened buffers" after the compilation "pulling" finishes.
I tried experimenting with compilation-finish-functions but found out that the functions added to the list will be executed after "every" compilation. Since I use compile to search IDs "gid" I don't want to refresh opened files on every search.
How can I wait on compilation to finish before refreshing opened files "only" while inside a command and not on every compile outside of the command.
Here is the code:
; From http://www.emacswiki.org/emacs/CompileCommand
(defun compile-pkg (&optional command startdir)
"Compile a package, moving up to the parent directory
containing configure.ac, if it exists. Start in startdir if defined,
else start in the current directory."
(interactive)
(let ((dirname) (dir-buffer nil))
(setq startdir (expand-file-name (if startdir startdir ".")))
(setq command (if command command compile-command))
(setq dirname (upward-find-file "Makefile" startdir))
; (setq dirname (if dirname dirname (upward-find-file "Makefile" startdir)))
; (setq dirname (if dirname dirname (expand-file-name ".")))
; We've now worked out where to start. Now we need to worry about
; calling compile in the right directory
(save-excursion
(setq dir-buffer (find-file-noselect dirname))
(set-buffer dir-buffer)
(compile command)
(kill-buffer dir-buffer)
)))
(defun upward-find-file (filename &optional startdir)
"Move up directories until we find a certain filename. If we
manage to find it, return the containing directory. Else if we
get to the toplevel directory and still can't find it, return
nil. Start at startdir or . if startdir not given"
(let ((dirname (expand-file-name
(if startdir startdir ".")))
(found nil) ; found is set as a flag to leave loop if we find it
(top nil)) ; top is set when we get
; to / so that we only check it once
; While we've neither been at the top last time nor have we found
; the file.
(while (not (or found top))
; If we're at / set top flag.
(if (string= (expand-file-name dirname) "/")
(setq top t))
; Check for the file
(if (file-exists-p (expand-file-name filename dirname))
(setq found t)
; If not, move up a directory
(setq dirname (expand-file-name ".." dirname))))
; return statement
(if found (concat dirname "/") nil)))
(defun compile-hgpull ()
(interactive)
(save-all-buffers)
(compile-pkg "hg pull -u")
; if (compile finished) -> (revert-all-buffers)
)
(global-set-key [f1] 'compile-hgpull)
compile is async. So, you have two choices.
One, don't use compile. Instead use one of the other ways to invoke a shell command, like shell-command or start-process or call-process. I think this is probably preferred; I don't see why you'd need to use compile here.
Two, set compilation-finish-function.
If you want to run a shell-command synchronously, and then see its output, it might be easier to use shell-command-to-string than compile.

Emacs: extending dired-do-compress to directories

If I press 'Z' in dired (or x-dired) mode in emacs, the file under the cursor is compressed or uncompressed.
I'd like to extend this behavior to directories. That is, if the cursor is at a directory "stuff", I'd like 'Z' to run
tar -zcf stuff.tgz stuff
(on the assumption that 'tar' is provided by the OS).
Side note:
The converse (expanding stuff.tgz into the full directory tree) can already be done by '!', which suggests guesses for expansion. The asymmetry ('Z' to compress and '!' to uncompress) does not bother me.
Cool idea. Here's a start that works for me: just need to change a few lines in dired-compress-file. I've highlighted the changes with BEGIN EDIT and END EDIT comments (sorry if there's a better way to highlight a diff). I'm sure this isn't robust, but maybe it can point you in the right direction.
EDIT: I should say that the below works for me in GNU Emacs 24.3.1.
(defun dired-compress-file (file)
;; Compress or uncompress FILE.
;; Return the name of the compressed or uncompressed file.
;; Return nil if no change in files.
(let ((handler (find-file-name-handler file 'dired-compress-file))
suffix newname
(suffixes dired-compress-file-suffixes))
;; See if any suffix rule matches this file name.
(while suffixes
(let (case-fold-search)
(if (string-match (car (car suffixes)) file)
(setq suffix (car suffixes) suffixes nil))
(setq suffixes (cdr suffixes))))
;; If so, compute desired new name.
(if suffix
(setq newname (concat (substring file 0 (match-beginning 0))
(nth 1 suffix))))
(cond (handler
(funcall handler 'dired-compress-file file))
((file-symlink-p file)
nil)
((and suffix (nth 2 suffix))
;; We found an uncompression rule.
(if (not (dired-check-process (concat "Uncompressing " file)
(nth 2 suffix) file))
newname))
(t
;;; We don't recognize the file as compressed, so compress it.
;;; Try gzip; if we don't have that, use compress.
(condition-case nil
;; BEGIN EDIT - choose the correct name if looking at a directory
(let ((out-name (if (file-directory-p file) (concat file ".tar.gz") (concat file ".gz"))))
;; END EDIT
(and (or (not (file-exists-p out-name))
(y-or-n-p
(format "File %s already exists. Really compress? "
out-name)))
;; BEGIN EDIT: create a tarball if we're looking at a directory
(not (if (file-directory-p file)
(dired-check-process (concat "Compressing " file)
"tar" "-zcf" out-name file)
(dired-check-process (concat "Compressing " file)
"gzip" "-f" file)))
;; END EDIT
(or (file-exists-p out-name)
(setq out-name (concat file ".z")))
;; Rename the compressed file to NEWNAME
;; if it hasn't got that name already.
(if (and newname (not (equal newname out-name)))
(progn
(rename-file out-name newname t)
newname)
out-name)))
(file-error
(if (not (dired-check-process (concat "Compressing " file)
"compress" "-f" file))
;; Don't use NEWNAME with `compress'.
(concat file ".Z"))))))))
The following is a link to the library dired-tar -- it tars and compresses directories and files. I have verified it works on OSX with a recent version of Emacs Trunk built on March 19, 2014.
http://www.emacswiki.org/emacs/DiredTar
This functionality is now installed in master:
press Z on a directory to run tar -czf dirname.tar.gz dirname
press Z on a *.tar.gz file to run tar -zxvf dirname
The latter can be customized via dired-compress-file-suffixes, if you ever need to use something other than -zxvf.

Using Emacs, is it possible to pin the compilation command to a specific buffer/directory?

Right now I am using the following to compile, when I'm in for example main.cpp
C-x b Makefile RET M-x compile RET RET
I actually have M-x compile as a keyboard shortcut, but the problem is I would really like not having to go through all that trouble to simply run my Makefile.
I need to visit Makefile to make sure the compile command is executed using the same directory. Is there any way to pin the directory so I can simply go M-x compile RET RET?
Best regards
Use recompile instead. C-u M-x recompile will let you edit the compile command first. Either way the compile will work out of the directory the last compile was done in.
See my answer here
Directory local variables provide an easy way to trigger the compile from a parent directory of any source file in a subdirectory.
I run emacs primarily on windows.
When I have a makefile that is in a parent directory of a C module, I use this as the compile command:
cd .. && nmake <arguments here>
for example:
cd .. && nmake CONFIG=Debug PLATFORM=x64 target
Beyond that, I find that specifying the make command line that I want to run for various modules is sort of a pain. I wanted a way to attach the default compile command to the buffer being edited. So I wrote a little elisp to handle that job. I figured to insert into the header comments of each buffer a line that would stipulate my preferred compile command, like this:
compile: cd .. && nmake CONFIG=Debug PLATFORM=x64 target
And then have a piece of elisp run, before I invoke M-x compile that grabs the line and proposes it as the compile command I would like to run.
This defun pulls a line out of the header comments:
(defun cheeso-c-get-value-from-comments (marker-string line-limit)
"gets a string from the header comments in the current buffer.
This is used to extract the compile command from the comments. It
could be used for other purposes too.
It looks for \"marker-string:\" and returns the string that
follows it, or returns nil if that string is not found.
eg, when marker-string is \"compile\", and the following
string is found at the top of the buffer:
compile: cl.exe /I uthash
...then this command will return the string
\"cl.exe /I uthash\"
It's ok to have whitespace between the marker and the following
colon.
"
(let (start search-limit found)
;; determine what lines to look in
(save-excursion
(save-restriction
(widen)
(cond ((> line-limit 0)
(goto-char (setq start (point-min)))
(forward-line line-limit)
(setq search-limit (point)))
((< line-limit 0)
(goto-char (setq search-limit (point-max)))
(forward-line line-limit)
(setq start (point)))
(t ;0 => no limit (use with care!)
(setq start (point-min))
(setq search-limit (point-max))))))
;; look in those lines
(save-excursion
(save-restriction
(widen)
(let ((re-string
(concat "\\b" marker-string "[ \t]*:[ \t]*\\(.+\\)$")))
(if (and start
(< (goto-char start) search-limit)
(re-search-forward re-string search-limit 'move))
(buffer-substring-no-properties
(match-beginning 1)
(match-end 1))))))))
Ok, now I need something to invoke that before I invoke compile.
(defun cheeso-invoke-compile-interactively ()
"fn to wrap the `compile' function. This simply
checks to see if `compile-command' has been previously set, and
if not, invokes `cheeso-guess-compile-command' to set the value.
Then it invokes the `compile' function, interactively."
(interactive)
(cond
((not (boundp 'cheeso-local-compile-command-has-been-set))
(cheeso-guess-compile-command)
(set (make-local-variable 'cheeso-local-compile-command-has-been-set) t)))
;; local compile command has now been set
(call-interactively 'compile))
Then of course, the defun that guesses the compile command:
(defun cheeso-guess-compile-command ()
"set `compile-command' intelligently depending on the
current buffer, or the contents of the current directory."
(interactive)
(set (make-local-variable 'compile-command)
(cond
(buffer-file-name
(let ((filename (file-name-nondirectory buffer-file-name)))
(cond
;; editing a C-language source file - check for an
;; explicitly-specified command
((string-equal (substring buffer-file-name -2) ".c")
(let ((explicit-compile-command
(cheeso-c-get-value-from-comments "compile" 34)))
(or explicit-compile-command
(concat "nmake " ;; assume a makefile exists
(file-name-sans-extension filename)
".exe"))))
;; editing a makefile - just run nmake
((string-equal (substring buffer-file-name -8) "makefile")
"nmake ")
;; something else - do a typical .exe build
(t
(concat "nmake "
(file-name-sans-extension filename)
".exe")))))
(t
;; punt
"nmake "))))
The final bit is to bind C-x C-e , normally bound to compile, to the wrapper defun:
(global-set-key "\C-x\C-e" 'cheeso-invoke-compile-interactively)
Now, when I do C-x C-e in the buffer, it searches for the compile command, and proposes to me the command that it finds. I can edit the proposed compile command, then press ENTER and run it.