Umask và quyền truy nhập file
Umask là gì?
Trong Unix, khi một file hay một thư mục được tạo ra thì quyền truy nhập đối với chúng (r, w, x) sẽ được xác định dựa trên hai giá trị là quyền truy nhập cơ sở (base permission) và mặt nạ (mask). Quyền truy nhập cơ sở là giá trị được thiết lập sẵn từ trước, đối với file là 666 (rw-rw-rw) và thư mục là 777 (rwxrwxrwx). Mặt nạ là giá trị đựợc thiết lập bởi người dùng bằng lệnh umask. Giá trị mặt nạ sẽ “che đi” một số bit trong quyền truy nhập cơ sở để tạo ra quyền truy nhập chính thức cho file (tương tự như cơ chế của subnet mask).
Cụ thể, quyền truy nhập chính thức được tính bằng cách lấy giá trị nhị phân của quyền truy nhập cơ sở AND với dạng biểu diễn bù 1 của mặt nạ. Ví dụ: Vì quyền truy nhập cơ sở của file là 666 (tức 110110110), nên nếu giá trị mask là 022 (000010010) thì quyền truy nhập chính thức của file sẽ là:
110 110 110 AND 111 101 101 = 110 100 100 = 644 (rw-r–r–)
Như vậy, các bit trong quyền truy nhập cơ sở ứng với các bit 1 của mask sẽ bị xóa. Cũng có thể tính quyền truy nhập chính thức đơn giản hơn bằng cách lấy 666 – 022 = 644.
Giá trị mặt nạ được thiết lập như thế nào?
Giá trị mask được thiết lập nhờ lệnh umask trên terminal hoặc bằng hàm hệ thống umask. Các file được tạo ra sau lệnh umask sẽ chịu tác động của giá trị mặt nạ mới. (more…)
Các user ID của một tiến trình trong Unix
Ba loại user ID của một tiến trình
Một tiến trình (process) trong Unix sở hữu 3 user ID sau đây:
Real user ID (user ID thật): Là user ID của người khởi động tiến trình.
Effective user ID (user ID hiệu dụng): Là một loại user ID được dùng trong những trường hợp sau đây:
- Khi tiến trình truy nhập một file, hệ điều hành kiểm tra xem effective user của tiến trình có quyền truy nhập file hay không.
- Khi tiến trình tạo mới một file, hệ điều hành lấy effective user của tiến trình làm chủ sở hữu (owner) của file.
- Khi tiến trình muốn gửi tín hiệu đến một tiến trình khác, hệ điều hành kiểm tra xem effective user của tiến trình gửi có quyền gửi tín hiệu hay không.
Saved user ID: Là giá trị của effective user ID được lưu trong bảng các tiến trình (process table). Mỗi tiến trình có một vùng dữ liệu tương ứng trong bảng các tiến trình. Saved user ID đóng vai trò như một bản backup cho effective user ID, được dùng khi tiến trình muốn quay lại effective user ID ban đầu.
Tương ứng với 3 loại user ID này là ba loại group ID: real group ID, effective group ID và saved group ID.
Vì sao cần effective user ID?
Effective user là một cơ chế quản lí truy nhập của Unix (access control hay authorization). Xem ví dụ sau đây: Alice là một kế toán và cô ta cần có quyền thay đổi file dữ liệu kế toán account_data của công ty. Tuy nhiên, để đảm bảo tính toàn vẹn dữ liệu, Alice không được phép thay đổi dữ liệu kế toán một cách thủ công mà phải thông qua một chương trình tên là account_program. Như vậy:
- Alice không có quyền ghi đối với account_data.
- account_program có quyền ghi đối với account_data.
- Alice có quyền chạy account_program.
Tuy nhiên, trong Unix chỉ có người dùng mới được coi là các chủ thể được cấp quyền sử dụng tài nguyên. Bởi vậy, giải pháp trong Unix là tạo ra một người dùng có quyền ghi đối với account_data, thiết lập effective user ID của account_program bằng ID của người dùng đó và trao cho Alice quyền chạy account_program. (more…)
Viết daemon trên Linux (2)
Nguồn: http://www.enderunix.org/docs/eng/daemon.php
Bài viết này bổ xung một số vấn đề chưa được trình bày trong bài viết “Viết daemon trên Linux (1)” của anh Hoàng.
1- Logging
Chúng ta có thể lựa chọn một trong hai cách ghi log như sau:
- Tự viết hàm ghi log:
Một hàm ghi log có dạng như sau:
void log_message( const char *fname, const char *msg ) { FILE *logfile = fopen( fname, “a” ); if( ! logfile ) return; fprintf( logfile, “%s\n”, msg ); fclose( logfile ); }
- Dùng các hàm có sẵn trong thư viện: Standard C library có các hàm syslog(), openlog(), closelog() phục vụ việc ghi log.
2- Cơ chế loại trừ lẫn nhau
Tại một thời điểm thường chỉ có một thực thể của daemon đang chạy. Một thực thể của daemon sẽ cố gắng khóa một file (lock file). Nếu khóa thành công nghĩa là chưa có thực thể nào khác của daemon đó đang chạy. Khi đó, pid của thực thể sẽ được ghi vào lock file. Chúng ta sử dụng hàm chuẩn lockf() như sau:
lfp = open( "exampled.lock", O_RDWR | O_CREAT, 0640 ); if ( lfp < 0 ) /* không mở được lock file */ exit( EXIT_FAILURE ); if ( lockf( lfp, F_TLOCK, 0 ) < 0 ) /* không khóa được lock file, một thực thể khác đang chạy */ exit( EXIT_SUCCESS ); /* chưa có thực thể nào đang chạy, ghi pid vào lock file */ sprintf( str,"%d\n", getpid() ); write( lfp, str, strlen(str) );
3- Bắt các tín hiệu gửi đến
Sau các bước chuẩn bị nói trên, một daemon sẽ bắt đầu lắng nghe tín hiệu gửi đến từ người dùng hoặc từ các tiến trình khác. Chúng ta viết một hàm xử lí tín hiệu, sau đó gán hàm đó với các tín hiệu cụ thể nhờ hàm chuẩn signal(): (more…)
Lấy xâu con trong shell script
Hôm trước nhân lúc nhàn rỗi mình có giúp một “đồng nghiệp” viết một script nhỏ bằng ZShell phục vụ cho việc testing. Do công việc hàng ngày chỉ là viết các script đơn giản nên mình đã khá lúng túng trước yêu cầu phải lấy ra được một xâu con (substring) từ một xâu khác. Lên Google search thì thấy cũng có khá nhiều người gặp khó khăn trước vấn đề này và hóa ra xử lí xâu trong shell cũng là một vấn đề khá thú vị. Bài viết này tổng kết những phương pháp để lấy substring trong shell mà tác giả lượm lặt trên mạng. Chú ý rằng những phương pháp này có thể không áp dụng được với một số loại shell nhất định.
Những kĩ thuật dưới đây được áp dụng để giải quyết bài toán cụ thể là lấy ra xâu “substring” từ xâu “this is a substring test”. Bài toán này có ý nghĩa trong trường hợp substring là một biến có thể nhận nhiều giá trị khác nhau.
1- Sử dụng cú pháp substring=${string:starting_position:length}
string=”this is a substring test” substring=${string:10:9} echo $substring substring
Chú ý rằng vị trí đầu tiên trong một xâu là 0. Nhược điểm của cách này là chỉ áp dụng khi xâu con cần lấy ra có độ dài cố định. Hơn nữa cách này không áp dụng được cho zshell, một loại shell được dùng phổ biến trên các hệ thống UNIX.
2- Sử dụng lệnh cut
Lệnh cut dùng để lấy ra một số kí tự trong một xâu. Dùng man cut để xem chi tiết về lệnh này. Trong bài toán cụ thể của chúng ta, trong xâu string thì xâu con “substring” bắt đầu từ vị trí 11 và kết thúc ở vị trí thứ 19. Bởi vậy có thể dùng lệnh sau đây để cắt ra “substring”
string=”this is a substring test” echo $string | cut –c11-19 substring
Nhược điểm của cách làm này là chỉ áp dụng với xâu con có độ dài cố định. Chúng ta có thể sử dụng lệnh cut với các tham số sau đây đề khắc phục nhược điểm đó
string=”this is a substring test” echo $string | cut –d’ ‘ –f4 substring
Lệnh trên lấy ra trường thứ 4 (f = field) của xâu string, mỗi trường cách nhau bởi kí tự space (d=delimeter).
3- Sử dụng các toán tử #, ##, % và %%
Toán tử # nghĩa là xóa bắt đầu từ bên trái xâu đầu tiên thỏa mãn mẫu (pattern) theo sau dấu #
Viết daemon trên Linux (1)
1. Daemon là gì?
Một daemon (hay service) là một background process được thiết kế để chạy độc lập, rất ít hoặc không có sự can thiệp của user. Daemon http của Apache web server là một ví dụ về daemon. Nó chạy ở dưới background, lắng nghe một số port xác định và cung cấp các pages hoặc processes scripts dựa vào kiểu request của user.
Để tạo một daemon trong Linux, chúng ta cần phải thực hiện một số bước theo thứ tự. Hiểu sâu bên trong cách một daemon hoạt động còn giúp chúng ta hiểu các hàm system call của kernel của Linux. Thực tế thì trong kernel module, có nhiều daemon quản lý các hardware device như là các mạch điều khiển ngoài, printer và PDAs. Chúng là một trong những khối cơ bản trên Linux mà nó cung cấp sự mềm dẻo và sức mạnh tuyệt vời.
Thông qua tài liệu này, chúng ta sẽ thể hiện một daemon rất đơn giản được viết bằng C. Như bạn có thể theo dõi từng bước, các đoạn mã sẽ được thêm vào chỉ ra thứ tự thực hiện các bước để thiết lập daemon và chạy nó.
2. Khởi đầu
Đầu tiên, bạn cần phải có những package sau được cài đặt trên Linux để phát triển các daemons:
- GCC 3.2.2 or higher
- Linux Development headers and libraries
Nếu hệ thống của bạn không có những package này bạn cần cài đặt nó. Để tìm version mà GCC bạn đang cài đặt, sử dụng lệnh:
gcc –-version3. Thiết kế
3.1 Nó sẽ làm gì?
Một daemon sẽ làm một việc nào đó và cần làm nó tốt. Nó có thể phức tạp như phải quản lý hàng trăm mailbox với rất nhiều domains, hoặc là đơn giản như việc viết một report và gọi sendmail để gửi report đó đi.
Trong bất kỳ trường hợp nào, bạn cần phải lên kế hoạch xem daemon sẽ làm gì. Nếu nó cần tương tác với các daemon khác (có thể bạn chưa viết) thì đó sẽ là điều cần xem xét.
3.2 Tương tác như thế nào
Daemon không nên trao đổi trực tiếp với user thông qua terminal. Thực tế, một daemon hoàn toàn không nên tương tác trực tiếp với user. Tất cả sự trao đổi nên thông qua một dạng giạo diện mà nó có thể phức tạp như một GTK+ GUI hay đơn giản như là một tập các tín hiệu.
4. Cấu trúc cơ bản của daemon
Khi một daemon khởi động, nó phải thực hiện một vài công việc ở mức thấp (low-level) để sẵn sàng thực hiện các công việc thực sự. Nó sẽ cần thực hiện các bước sau:
Unix programming with standard I/O (2)
Phần 2: Chương trình hiển thị nội dung file theo từng trang màn hình
1. Đặt vấn đề
Khi xem nội dung một file dài, chúng ta thường muốn nội dung file đó được hiển thị lần lượt theo từng trang màn hình. Hai lệnh phổ biến để xem nội dung file là cat và more không đáp ứng được nhu cầu này (chúng không có khả năng này, hoặc có nhưng không tiện dụng, “tác giả” cũng không biết rõ về tất cả các khả năng của hai lệnh này). Bởi vậy, chúng ta sẽ phát triển một chương trình tên là p làm nhiệm vụ in ra nội dung một file theo từng trang màn hình (screenful-at-a-time). Chương trình sẽ đợi người dùng ấn một phím để chuyển sang hiển thị trang tiếp theo. Giống như vis, p nhận dữ liệu vào từ cả file lẫn standard input. Ví dụ:
p nhận dữ liệu vào từ file
$ p vis.c
p nhận dữ liệu vào từ standard input
$ grep ‘#define’ *.[ch] | p
Ở phiên bản đầu tiên, p sẽ hiển thị nội dung file theo từng khối, mỗi khối 22 dòng (phần lớn các terminal gồm 24 dòng văn bản). Một cách đơn giản để nhắc người dùng ấn một phím để tiếp tục là không in ra kí tự new line nằm cuối dòng thứ 22. Khi đó, con trỏ sẽ nằm ở cuối dòng thứ 22 thay vì đầu dòng thứ 23. Khi người dùng ấn phím enter, kí tự new line còn thiếu của dòng 22 sẽ được thêm vào, nhờ đó dòng tiếp theo (dòng 23) sẽ được in ra ở đúng vị trí của nó. Nếu người dùng ấn ctrl-d hay q thay vì enter, p sẽ kết thúc. Chúng ta sẽ không quan tâm đến các dòng quá dài. Ngoài ra, khi hiển thị nhiều file cùng lúc thì nội dung các file sẽ được in ra liên tục mà không có sự phân cách gì cả. Tức là, đầu ra của hai lệnh sau đây là như nhau
$ p file1 file2 …
và
$ cat file1 file2 … | p
Chú ý rằng lệnh cat in ra nội dung các file một cách liên tục mà không có sự phân cách gì cả. Nếu muốn nội dung các file được phân cách bởi tên file, chúng ta có thể dùng vòng lặp sau đây (ôn lại lập trình shell luôn
)
$ for i in filenames > do > echo $i: > cat $i > done | p
Có rất nhiều tính năng có thể được đưa vào chương trình p. Quan điểm của chúng ta là: Trước hết tạo ra một phiên bản đơn giản, sau đó dần dần thêm vào các tính năng phức tạp hơn khi cần thiết. Những tính năng được thêm vào phải là những cái mà người dùng thực sự muốn, chứ không phải những cái mà chúng ta nghĩ rằng họ muốn.
2. Phiên bản đầu tiên của p
Cấu trúc của p cũng tương tự như vis: Hàm main duyệt qua các file đầu vào và gọi một hàm tên là print để xử lí từng file. Dưới đây là hàm main
Unix programming with standard I/O (1)
Lời tựa
Dạo này đang thời kì nông nhàn nên quay lại với blog. Gần một tháng nay không có bài viết mới nào, có lẽ do các blogger đều đang bận bịu với những kế hoạch riêng của mình. Dạo này không thích những thứ loằng ngoằng phức tạp nữa, cũng chả thích mình là guru nữa, đau đầu lắm
. Bây giờ chỉ thích những thứ đơn giản nhưng đẹp đẽ. Ngẫm ra làm phần mềm cũng chẳng cần đến những thứ cao siêu hay phức tạp, vấn đề chỉ là kết hợp những thứ thật đơn giản thành một hệ thống lớn làm việc được mà thôi.
Đấy là lí do mà dạo này “tác giả” quay sang đọc cuốn “The Unix programming environment” của Brian W.Kernighan và Rob Pike. Công nhận hai bác này viết sách cực hay. Cuốn sách không chỉ trình bày các công cụ hay ngôn ngữ mà quan trọng hơn là nêu bật lên được triết lí của Unix: Viết các chương trình đơn giản, mỗi chương trình chỉ làm một nhiệm vụ nhưng có khả năng tương tác tốt với nhau để thực hiện những nhiệm vụ phức tạp hơn.
“Tác giả” bài viết này, trong thời gian tạm thời chưa bị phân tâm bởi việc fix bug nhàm chán thường nhật, muốn trích ra đây một số phần của cuốn sách, vừa để giải trí, vừa để ôn lại C và Unix, cũng vừa là để học tập tư duy lập trình trên Unix. Đồng thời, việc làm này cũng khôi phục lại phần nào tình cảm của “tác giả” đối với công việc lập trình, vốn đã bị phai nhạt khá nhiều sau một thời gian làm việc trong một dự án chán ngắt và không hề thể hiện được một chút gì gọi là “vẻ đẹp” của lập trình Unix.
Tác giả cũng khuyến cáo các độc giả (nếu có
) nên tìm đọc bản tiếng Anh của cuốn sách (tác giả cũng không có bản mềm, chỉ có bản cứng), bởi trình độ kĩ thuật và tiếng Anh của tác giả đều chỉ ở mức sơ cấp.
Loạt bài viết này trích ra từ các chương 6, 7, 8, 9 của cuốn “The Unix programming environment”. Các chương này trình bày quá trình phát triển một số chương trình đơn giản, qua đó làm nổi bật lên các kĩ thuật và các triết lí lập trình trên Unix. Các chương trình này, theo tác giả cuốn sách, đều là các chương trình nhỏ và không có mặt trong 7th Edition của Unix. Nếu hệ điều hành hiện thời của bạn cũng không có chúng, bạn sẽ thấy chúng rất có ích. Trong trường hợp ngược lại, việc so sánh thiết kế của những chương trình sẵn có với những chương trình trong sách cũng mang lại nhiều điều bổ ích.
Chúng ta cùng bắt đầu với các chương trình nhập xuất dữ liệu (I/O).
Phần 1 Chương trình in ra các kí tự đặc biệt: VIS
1. Standard input and output: vis
Rất nhiều chương trình đọc dữ liệu từ một đầu vào và ghi dữ liệu ra một đầu ra. Khi đó việc sử dụng các thao tác vào ra (I/O) đối với đầu vào chuẩn (standard input) và đầu ra chuẩn (standard output) là đủ đáp ứng yêu cầu của chương trình.
Chúng ta hãy bắt đầu xây dựng một chương trình đơn giản tên là vis (viết tắt của visible). vis sao chép dữ liệu từ standard input sang standard output, đồng thời hiển thị tất cả các kí tự vốn không thể in được (non-printing character) dưới dạng \nnn, trong đó nnn là giá trị octal của kí tự đó. Ví dụ: Chúng ta chuẩn bị file đầu vào x như sau:
$ cat x abc<ctrl -a> def</ctrl><ctrl -b> </ctrl><ctrl -d></ctrl>
Sử dụng vis để hiện thị x, kí tự ctrl-a sẽ được hiển thị là \001, kí tự ctrl-b sẽ được hiển thị là \002.
$ vis x abc\001 def\002
Chương trình vis sẽ có ích trong việc phát hiện các kí tự lạ được ghi vào file vì một lí do nào đó. Chú ý rằng phiên bản đầu tiên của vis sẽ chỉ nhận dữ liệu vào từ standard input chứ chưa có khả năng đọc file. Khi cần in ra nội dung của nhiều file, chúng ta có thể nhờ đến lệnh cat như sau:
$ cat file1 file2 file3 … | vis
Chương trình vis của chúng ta sẽ có chức năng giống với lệnh sed sau đây:
$ sed –n l x
abc\01
def\02Tuy nhiên trong một số trường hợp sed chỉ làm việc được với các file văn bản (text), bởi vậy việc viết mới chương trình vis vẫn là cần thiết.
Trong phiên bản đầu tiên của vis, chúng ta sử dụng hai hàm getchar và putchar. Hàm getchar đọc vào kí tự tiếp theo từ standard input (mặc định là terminal, cũng có thể là một file hay một pipeline, các chương trình không biết điều đó). Hàm putchar ghi một kí tự ra standard output, mặc định cũng là terminal.
Dưới đây là phiên bản đầu tiên của vis
The stream editor sed
Nguồn: Tổng hợp từ cuốn sách “The Unix Programming Environment” của Brian W. Kernighan và Rob Pike.
Bài viết này giới thiệu những chức năng cơ bản nhất của sed, một chương trình xử lí văn bản cực mạnh trong Unix. Bài viết có sử dụng (mà không giải thích chi tiết) một số regular expression (cơ bản) và một số lệnh Unix (cũng cơ bản
)
Cú pháp chung của sed
Cú pháp chung của một lệnh sed là
sed 'danh sách các lệnh' 'danh sách các file đầu vào'
sed sẽ đọc từng dòng của các file đầu vào và thực hiện từng lệnh trong danh sách các lệnh đối với các dòng đó. Sau đó, sed ghi dữ liệu ra vào thiết bị đầu ra chuẩn.
Chẳng hạn, chúng ta có thể thay tất cả các từ UNIX trong một file thành UNIX(TM) bằng câu lệnh sau:
sed 's/UNIX/UNIX(TM)/g' filename
Khi thực hiện lệnh này, sed sẽ:
- Đọc vào từng dòng của filename
- Đối với mỗi dòng đọc vào, sed sẽ thực hiện lệnh s/UNIX/UNIX(TM)/g, tức là thay thế (s = substitute) tất cả (g = global) các từ UNIX bởi UNIX(TM).
s (substitute) là một lệnh của ed, chương trình tiền thân của sed.
Chú ý: sed không thay đổi nội dung của file đầu vào.
Chúng ta luôn sử dụng dấu nháy đơn (single quote) để bao quanh các lệnh của sed nhằm tránh trường hợp các kí tự đặc biệt bị dịch thành ý nghĩa khác bởi shell.
Sau đây là 1 số lệnh sed thú vị!!!
POSIX Thread (7) – Condition Variables
Chúng ta đã biết cách thức dùng mutex để đồng bộ hóa giữa các thread, tránh xung đột giữa các thread khi cùng truy cập tài nguyên. Tuy nhiên thì không phải mutex làm được tất cả. Ví dụ như chúng ta sẽ làm thế nào nếu chúng ta muốn thread đợi một điều kiện nào đó xảy ra với dữ liệu trong vùng chia sẻ? Chắc chắn là chúng ta sẽ làm bằng cách lặp liên tục việc khóa và mở khóa mutex (để đảm bảo đồng bộ giữa các thread cùng truy nhập vào vùng dữ liệu chia sẻ) và kiểm tra bất cứ sự thay đổi nào trên dữ liệu. Cùng lúc đó sẽ rất nhanh chóng thread sẽ mở khóa mutex cho các thread khác có thể thực hiện sự thay đổi trên vùng dữ liệu đó. Như vậy thì cách tiếp cận này thật kinh khủng bởi vì chúng ta sẽ cần một vòng lặp busy-loop để nhận ra sự thay đổi ở vùng dữ liệu. Nó thật là “lãng phí CPU”. (Bạn cần phải phân biệt giữa một chương trình ở trạng thái idle và busy!!!).
Chúng ta có thể để cho thread “ngủ” một chút, ví dụ 3 giây chẳng hạn ở giữa mỗi một lần kiểm tra, nhưng rõ ràng nó không tối ưu tuyệt đối. Cái chúng ta cần là phải đưa thread đó vào trạng thái ngủ cho đến khi một “điều kiện” nào đó được thỏa mãn. Một khi điều kiện được thỏa mãn thì nó sẽ đánh thức thread của chúng ta để nó tiếp tục sử lý. Đó chính là một kiểu cơ chế báo hiệu (signal). Khi có tín hiệu thì thread mới được đánh thức để sử lý tín hiệu đó.
Trong bài này chúng ta sẽ sử dụng biến pthread_condition_t để thể hiện cách thức báo hiệu này. Biến pthread_condition_t có cách sử dụng cũng tương tự như biến mutex. Chúng ta khai báo và khởi tạo như sau:
POSIX Thread (6) – Mutex
Chúng ta đã hiểu vấn đề nảy sinh ở chương trình trước. Để giải quyết bài toán xung đột đó chũng ta hãy xem đoạn mã đúng sử dụng mutex:
thread3.c
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int myglobal;
pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
void *thread_function(void *arg)
{
int i,j;
for ( i=0; i<20; i++ ) {
pthread_mutex_lock(&mymutex);
j=myglobal;
j=j+1;
printf(".");
fflush(stdout);
sleep(1);
myglobal=j;
pthread_mutex_unlock(&mymutex);
}
return NULL;
}
int main(void)
{
pthread_t mythread;
int i;
if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
printf("error creating thread.");
abort();
}
for ( i=0; i<20; i++) {
pthread_mutex_lock(&mymutex);
myglobal=myglobal+1;
pthread_mutex_unlock(&mymutex);
printf("o");
fflush(stdout);
sleep(1);
}
if ( pthread_join ( mythread, NULL ) ) {
printf("error joining thread.");
abort();
}
printf("\nmyglobal equals %d\n",myglobal);
exit(0);
}